diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextActiveStreamingToolsTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextActiveStreamingToolsTest.java new file mode 100755 index 000000000..54ef01fff --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextActiveStreamingToolsTest.java @@ -0,0 +1,595 @@ +/* + * 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=activeStreamingTools_c4cc5030aa +ROOST_METHOD_SIG_HASH=activeStreamingTools_398f84db97 + +Scenario 1: Verify That activeStreamingTools() Returns an Empty Map on a Freshly Built InvocationContext + +Details: + TestName: activeStreamingToolsReturnsEmptyMapOnFreshContext + Description: Verifies that when an InvocationContext is newly constructed using the builder pattern with no streaming tools added, the activeStreamingTools() method returns an empty (but non-null) map. This reflects the field initialization: private final Map activeStreamingTools = new ConcurrentHashMap<>(). + +Execution: + Arrange: + - Create a mock or stub for BaseSessionService. + - Create a mock or stub for BaseArtifactService. + - Create a mock or stub for Session. + - Create a mock or stub for BaseAgent. + - Build an InvocationContext using InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(). + Act: + - Call invocationContext.activeStreamingTools(). + Assert: + - Assert that the returned map is not null. + - Assert that the returned map is empty (size == 0). + +Validation: + The assertion ensures that a newly created InvocationContext starts with no active streaming tools, which is the expected initial state. This is important because downstream logic that iterates over or queries active streaming tools must handle the empty-map baseline without null pointer issues. + +--- + +Scenario 2: Verify That activeStreamingTools() Returns a Non-Null Map Reference + +Details: + TestName: activeStreamingToolsReturnsNonNullMap + Description: Ensures that the activeStreamingTools() method never returns null, even when called immediately after construction. Since the backing field is initialized inline to a new ConcurrentHashMap<>(), a null return would be a defect. + +Execution: + Arrange: + - Create mocks for BaseSessionService, BaseArtifactService, Session, and BaseAgent. + - Build an InvocationContext via the builder with only the required fields set. + Act: + - Call invocationContext.activeStreamingTools(). + Assert: + - Use assertNotNull to confirm the returned Map is not null. + +Validation: + Guarantees that callers can safely use the returned map reference without performing a null check. This is a fundamental contract of the method that underpins all other streaming-tool management logic within the application. + +--- + +Scenario 3: Verify That activeStreamingTools() Returns the Live Map That Reflects Entries Added Directly to It + +Details: + TestName: activeStreamingToolsReflectsDirectlyAddedEntries + Description: Confirms that the Map returned by activeStreamingTools() is the actual live backing map. When an entry is put into the returned map directly, a subsequent call to activeStreamingTools() must include that entry, demonstrating that the method exposes the internal ConcurrentHashMap by reference. + +Execution: + Arrange: + - Create mocks for the required dependencies and build an InvocationContext. + - Create a mock or stub for ActiveStreamingTool. + - Obtain the map via invocationContext.activeStreamingTools() and store it in a local variable (mapRef). + Act: + - Put a new entry into mapRef: mapRef.put("tool-call-id-1", mockActiveStreamingTool). + - Call invocationContext.activeStreamingTools() again. + Assert: + - Assert that the newly returned map contains the key "tool-call-id-1". + - Assert that the value associated with "tool-call-id-1" equals mockActiveStreamingTool. + - Assert that the map size is 1. + +Validation: + This verifies that activeStreamingTools() exposes the actual mutable backing map, not a defensive copy. This behavior is critical for components that need to register or deregister streaming tools during an invocation lifecycle. + +--- + +Scenario 4: Verify That activeStreamingTools() Returns a Map Consistent with the State Populated via copyOf() + +Details: + TestName: activeStreamingToolsReturnsCopiedEntriesAfterCopyOf + Description: Tests that when InvocationContext.copyOf(other) is used to create a shallow copy, the activeStreamingTools map in the new context contains all entries from the original context's activeStreamingTools map, and activeStreamingTools() on the copy reflects them. + +Execution: + Arrange: + - Build an original InvocationContext using the builder. + - Create a mock ActiveStreamingTool and add it to the original context's map: + originalContext.activeStreamingTools().put("tool-id-A", mockTool). + - Call InvocationContext.copyOf(originalContext) to create a copied context. + Act: + - Call copiedContext.activeStreamingTools(). + Assert: + - Assert that the returned map is not null. + - Assert that the returned map contains the key "tool-id-A". + - Assert that the value for "tool-id-A" equals the original mock tool. + - Assert that the map size is 1. + +Validation: + Validates that the copyOf() method correctly transfers all active streaming tool entries via putAll(), and that the activeStreamingTools() method on the copy correctly exposes those entries. This is essential for scenarios where invocation contexts are forked or delegated. + +--- + +Scenario 5: Verify That activeStreamingTools() on the Copy and Original Are Independent Maps After copyOf() + +Details: + TestName: activeStreamingToolsOfCopyAndOriginalAreIndependentAfterCopyOf + Description: After creating a copy with InvocationContext.copyOf(), adding a new entry to the original's activeStreamingTools map should not affect the copied context's activeStreamingTools map, and vice versa. The two maps should be independent ConcurrentHashMap instances. + +Execution: + Arrange: + - Build an original InvocationContext. + - Create copiedContext = InvocationContext.copyOf(originalContext). + - Create two separate mock ActiveStreamingTool objects: mockToolA and mockToolB. + Act: + - Add mockToolA to the original: originalContext.activeStreamingTools().put("tool-A", mockToolA). + - Add mockToolB to the copy: copiedContext.activeStreamingTools().put("tool-B", mockToolB). + Assert: + - Assert that originalContext.activeStreamingTools() contains "tool-A" but NOT "tool-B". + - Assert that copiedContext.activeStreamingTools() contains "tool-B" but NOT "tool-A". + +Validation: + Ensures that the shallow copy creates a new independent backing ConcurrentHashMap, so mutations in one context do not leak into the other. This is vital for multi-agent delegation scenarios where sub-agents must maintain their own streaming tool registries. + +--- + +Scenario 6: Verify That activeStreamingTools() Returns a Map with Multiple Entries When Multiple Tools Are Added + +Details: + TestName: activeStreamingToolsReturnsMapWithMultipleEntries + Description: Validates that activeStreamingTools() correctly reflects multiple active streaming tool entries when several tools are registered in the map, ensuring the ConcurrentHashMap supports multiple concurrent entries. + +Execution: + Arrange: + - Build an InvocationContext. + - Create three mock ActiveStreamingTool instances: mockTool1, mockTool2, mockTool3. + Act: + - Add all three tools: context.activeStreamingTools().put("call-id-1", mockTool1), + context.activeStreamingTools().put("call-id-2", mockTool2), + context.activeStreamingTools().put("call-id-3", mockTool3). + - Call context.activeStreamingTools() to retrieve the map. + Assert: + - Assert that the map size equals 3. + - Assert that the map contains key "call-id-1" with value mockTool1. + - Assert that the map contains key "call-id-2" with value mockTool2. + - Assert that the map contains key "call-id-3" with value mockTool3. + +Validation: + Confirms that the method correctly exposes all registered streaming tools. In real-world usage, multiple tools may be streaming concurrently during a single invocation, and each must be independently accessible by its tool call ID. + +--- + +Scenario 7: Verify That activeStreamingTools() Returns a Map That Supports Entry Removal + +Details: + TestName: activeStreamingToolsSupportsEntryRemoval + Description: Validates that after adding an entry to the active streaming tools map and subsequently removing it via the returned map reference, the map is empty again. This ensures the returned map is fully mutable and not a read-only view. + +Execution: + Arrange: + - Build an InvocationContext. + - Create a mock ActiveStreamingTool. + - Add the tool: context.activeStreamingTools().put("tool-xyz", mockTool). + Act: + - Remove the entry: context.activeStreamingTools().remove("tool-xyz"). + - Call context.activeStreamingTools() again to get the current state. + Assert: + - Assert that the returned map does not contain the key "tool-xyz". + - Assert that the returned map is empty (size == 0). + +Validation: + Ensures that the streaming tool map supports removal operations, which is necessary for deregistering tools once they complete their streaming lifecycle within an invocation. + +--- + +Scenario 8: Verify That activeStreamingTools() Is Consistent with the Map Used in the equals() Method + +Details: + TestName: activeStreamingToolsIsConsistentWithEquality + Description: Validates that the activeStreamingTools map participates correctly in the equals() comparison between two InvocationContext instances. Two contexts that are identical except for their activeStreamingTools content should not be considered equal. + +Execution: + Arrange: + - Build two InvocationContext instances (contextA and contextB) with identical builder configurations (same sessionService, artifactService, agent, session, etc.). + - Create a mock ActiveStreamingTool. + - Add the tool to contextA only: contextA.activeStreamingTools().put("tool-1", mockTool). + Act: + - Call contextA.equals(contextB). + Assert: + - Assert that contextA.equals(contextB) returns false. + +Validation: + Verifies that the activeStreamingTools map is properly included in the equals() logic (as seen in the equals() method override), confirming that two contexts with differing active tool registrations are not incorrectly treated as equal. This is important for caching, deduplication, or event-routing logic that uses equality comparison. + +--- + +Scenario 9: Verify That activeStreamingTools() Returns the Same Map Instance on Repeated Calls + +Details: + TestName: activeStreamingToolsReturnsSameMapInstanceOnRepeatedCalls + Description: Confirms that multiple calls to activeStreamingTools() on the same InvocationContext instance return the exact same map reference (identity equality), since the backing field is a final field initialized once. + +Execution: + Arrange: + - Build an InvocationContext. + Act: + - Call context.activeStreamingTools() and store the result as mapRef1. + - Call context.activeStreamingTools() again and store as mapRef2. + Assert: + - Use assertSame to verify that mapRef1 and mapRef2 are the identical object reference. + +Validation: + Ensures referential stability of the returned map. Because the backing field is final and initialized to a single ConcurrentHashMap, the method must always return the same object. Callers who cache the map reference (e.g., in a local variable) can rely on it being current and consistent. + +--- + +Scenario 10: Verify That activeStreamingTools() Returns an Empty Map Even When Other Context Fields Are Populated + +Details: + TestName: activeStreamingToolsReturnsEmptyMapWhenOtherFieldsArePopulated + Description: Checks that populating other InvocationContext fields (such as branch, invocationId, userContent, etc.) has no side-effect on the activeStreamingTools map, which should remain empty unless explicitly populated. + +Execution: + Arrange: + - Build an InvocationContext with all available builder fields populated: + - sessionService(mockSessionService) + - artifactService(mockArtifactService) + - memoryService(mockMemoryService) + - agent(mockAgent) + - session(mockSession) + - invocationId("test-invocation-id") + - branch("branch-1") + - userContent(mockContent) + - endInvocation(true) + Act: + - Call context.activeStreamingTools(). + Assert: + - Assert that the returned map is not null. + - Assert that the returned map is empty. + +Validation: + Confirms that the activeStreamingTools map is completely independent of other context fields and is only populated through direct manipulation of the map. This reinforces the separation of concerns within InvocationContext. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.genai.types.FunctionCall; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextActiveStreamingToolsTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private PluginManager mockPluginManager; + @Mock + private Session mockSession; + @Mock + private BaseAgent mockAgent; + @Mock + private Content mockContent; + @Mock + private ActiveStreamingTool mockActiveStreamingTool; + @Mock + private ActiveStreamingTool mockActiveStreamingTool2; + @Mock + private ActiveStreamingTool mockActiveStreamingTool3; + @BeforeEach + void setUp() { + lenient().when(mockSession.appName()).thenReturn("testApp"); + lenient().when(mockSession.userId()).thenReturn("testUser"); + lenient().when(mockSession.id()).thenReturn("testSession"); + } + private InvocationContext buildBasicContext() { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + } + @Test + @Tag("valid") + void activeStreamingToolsReturnsEmptyMapOnFreshContext() { + // Arrange + InvocationContext invocationContext = buildBasicContext(); + // Act + Map result = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(result, "activeStreamingTools() should not return null"); + assertTrue(result.isEmpty(), "activeStreamingTools() should return an empty map on a fresh context"); + assertEquals(0, result.size(), "activeStreamingTools() map size should be 0 on a fresh context"); + } + @Test + @Tag("valid") + void activeStreamingToolsReturnsNonNullMap() { + // Arrange + InvocationContext invocationContext = buildBasicContext(); + // Act + Map result = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(result, "activeStreamingTools() must never return null"); + } + @Test + @Tag("valid") + void activeStreamingToolsReflectsDirectlyAddedEntries() { + // Arrange + InvocationContext invocationContext = buildBasicContext(); + Map mapRef = invocationContext.activeStreamingTools(); + // Act + mapRef.put("tool-call-id-1", mockActiveStreamingTool); + Map resultAfterAdd = invocationContext.activeStreamingTools(); + // Assert + assertTrue(resultAfterAdd.containsKey("tool-call-id-1"), + "Map should contain the key 'tool-call-id-1' after direct insertion"); + assertEquals(mockActiveStreamingTool, resultAfterAdd.get("tool-call-id-1"), + "Map should return the exact tool associated with 'tool-call-id-1'"); + assertEquals(1, resultAfterAdd.size(), "Map size should be 1 after adding one entry"); + } + @Test + @Tag("integration") + void activeStreamingToolsReturnsCopiedEntriesAfterCopyOf() { + // Arrange + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("original-invocation-id") + .build(); + originalContext.activeStreamingTools().put("tool-id-A", mockActiveStreamingTool); + // Act + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + Map copiedMap = copiedContext.activeStreamingTools(); + // Assert + assertNotNull(copiedMap, "activeStreamingTools() on copied context should not return null"); + assertTrue(copiedMap.containsKey("tool-id-A"), + "Copied context's map should contain key 'tool-id-A' from original"); + assertEquals(mockActiveStreamingTool, copiedMap.get("tool-id-A"), + "Copied context's map should have the same tool value for 'tool-id-A'"); + assertEquals(1, copiedMap.size(), "Copied context's map size should be 1"); + } + @Test + @Tag("integration") + void activeStreamingToolsOfCopyAndOriginalAreIndependentAfterCopyOf() { + // Arrange + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("original-invocation-id") + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + ActiveStreamingTool mockToolA = mock(ActiveStreamingTool.class); + ActiveStreamingTool mockToolB = mock(ActiveStreamingTool.class); + // Act + originalContext.activeStreamingTools().put("tool-A", mockToolA); + copiedContext.activeStreamingTools().put("tool-B", mockToolB); + // Assert + assertTrue(originalContext.activeStreamingTools().containsKey("tool-A"), + "Original context should contain 'tool-A'"); + assertFalse(originalContext.activeStreamingTools().containsKey("tool-B"), + "Original context should NOT contain 'tool-B' added to the copy"); + assertTrue(copiedContext.activeStreamingTools().containsKey("tool-B"), + "Copied context should contain 'tool-B'"); + assertFalse(copiedContext.activeStreamingTools().containsKey("tool-A"), + "Copied context should NOT contain 'tool-A' added to the original"); + } + @Test + @Tag("valid") + void activeStreamingToolsReturnsMapWithMultipleEntries() { + // Arrange + InvocationContext context = buildBasicContext(); + // Act + context.activeStreamingTools().put("call-id-1", mockActiveStreamingTool); + context.activeStreamingTools().put("call-id-2", mockActiveStreamingTool2); + context.activeStreamingTools().put("call-id-3", mockActiveStreamingTool3); + Map result = context.activeStreamingTools(); + // Assert + assertEquals(3, result.size(), "Map should contain exactly 3 entries"); + assertTrue(result.containsKey("call-id-1"), "Map should contain key 'call-id-1'"); + assertEquals(mockActiveStreamingTool, result.get("call-id-1"), + "Map should return mockTool1 for 'call-id-1'"); + assertTrue(result.containsKey("call-id-2"), "Map should contain key 'call-id-2'"); + assertEquals(mockActiveStreamingTool2, result.get("call-id-2"), + "Map should return mockTool2 for 'call-id-2'"); + assertTrue(result.containsKey("call-id-3"), "Map should contain key 'call-id-3'"); + assertEquals(mockActiveStreamingTool3, result.get("call-id-3"), + "Map should return mockTool3 for 'call-id-3'"); + } + @Test + @Tag("valid") + void activeStreamingToolsSupportsEntryRemoval() { + // Arrange + InvocationContext context = buildBasicContext(); + context.activeStreamingTools().put("tool-xyz", mockActiveStreamingTool); + assertEquals(1, context.activeStreamingTools().size(), + "Map should have 1 entry before removal"); + // Act + context.activeStreamingTools().remove("tool-xyz"); + Map resultAfterRemoval = context.activeStreamingTools(); + // Assert + assertFalse(resultAfterRemoval.containsKey("tool-xyz"), + "Map should not contain 'tool-xyz' after removal"); + assertTrue(resultAfterRemoval.isEmpty(), + "Map should be empty after removing the only entry"); + assertEquals(0, resultAfterRemoval.size(), "Map size should be 0 after removal"); + } + @Test + @Tag("valid") + void activeStreamingToolsIsConsistentWithEquality() { + // Arrange + InvocationContext contextA = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("same-invocation-id") + .build(); + InvocationContext contextB = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("same-invocation-id") + .build(); + // Act + contextA.activeStreamingTools().put("tool-1", mockActiveStreamingTool); + // Assert + assertFalse(contextA.equals(contextB), + "Contexts with different activeStreamingTools should not be equal"); + } + @Test + @Tag("valid") + void activeStreamingToolsReturnsSameMapInstanceOnRepeatedCalls() { + // Arrange + InvocationContext context = buildBasicContext(); + // Act + Map mapRef1 = context.activeStreamingTools(); + Map mapRef2 = context.activeStreamingTools(); + // Assert + assertSame(mapRef1, mapRef2, + "Multiple calls to activeStreamingTools() should return the same map instance"); + } + @Test + @Tag("valid") + void activeStreamingToolsReturnsEmptyMapWhenOtherFieldsArePopulated() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .branch("branch-1") + .userContent(mockContent) + .endInvocation(true) + .build(); + // Act + Map result = context.activeStreamingTools(); + // Assert + assertNotNull(result, "activeStreamingTools() should not return null even when other fields are populated"); + assertTrue(result.isEmpty(), + "activeStreamingTools() should still return empty map when only other fields are populated"); + } + @Test + @Tag("valid") + void activeStreamingToolsMapIsMutableAndSupportsReplace() { + // Arrange + InvocationContext context = buildBasicContext(); + ActiveStreamingTool replacementTool = mock(ActiveStreamingTool.class); + context.activeStreamingTools().put("tool-key", mockActiveStreamingTool); + assertEquals(mockActiveStreamingTool, context.activeStreamingTools().get("tool-key"), + "Initial tool should be present before replace"); + // Act + context.activeStreamingTools().put("tool-key", replacementTool); + // Assert + assertEquals(replacementTool, context.activeStreamingTools().get("tool-key"), + "Map should reflect the replaced tool for the same key"); + assertEquals(1, context.activeStreamingTools().size(), + "Map size should remain 1 after replacing an entry"); + } + @Test + @Tag("boundary") + void activeStreamingToolsMapHandlesEmptyStringKey() { + // Arrange + InvocationContext context = buildBasicContext(); + // Act + context.activeStreamingTools().put("", mockActiveStreamingTool); + // Assert + assertTrue(context.activeStreamingTools().containsKey(""), + "Map should support an empty string as a key"); + assertEquals(mockActiveStreamingTool, context.activeStreamingTools().get(""), + "Map should return the tool associated with an empty string key"); + } + @Test + @Tag("boundary") + void activeStreamingToolsMapHandlesLargeNumberOfEntries() { + // Arrange + InvocationContext context = buildBasicContext(); + int numberOfEntries = 1000; + // Act + for (int i = 0; i < numberOfEntries; i++) { + ActiveStreamingTool tool = mock(ActiveStreamingTool.class); + context.activeStreamingTools().put("tool-" + i, tool); + } + // Assert + assertEquals(numberOfEntries, context.activeStreamingTools().size(), + "Map should contain exactly " + numberOfEntries + " entries"); + assertTrue(context.activeStreamingTools().containsKey("tool-0"), + "Map should contain the first added key 'tool-0'"); + assertTrue(context.activeStreamingTools().containsKey("tool-" + (numberOfEntries - 1)), + "Map should contain the last added key 'tool-" + (numberOfEntries - 1) + "'"); + } + @Test + @Tag("integration") + void activeStreamingToolsMapIsIndependentAfterCopyOfWithMultipleEntries() { + // Arrange + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("original-id") + .build(); + ActiveStreamingTool toolA = mock(ActiveStreamingTool.class); + ActiveStreamingTool toolB = mock(ActiveStreamingTool.class); + originalContext.activeStreamingTools().put("key-A", toolA); + originalContext.activeStreamingTools().put("key-B", toolB); + // Act + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + // Clear the original map after copy + originalContext.activeStreamingTools().clear(); + // Assert + assertTrue(originalContext.activeStreamingTools().isEmpty(), + "Original context map should be empty after clear"); + assertFalse(copiedContext.activeStreamingTools().isEmpty(), + "Copied context map should not be affected by clearing original map"); + assertEquals(2, copiedContext.activeStreamingTools().size(), + "Copied context should still have 2 entries"); + assertTrue(copiedContext.activeStreamingTools().containsKey("key-A"), + "Copied context should contain 'key-A'"); + assertTrue(copied \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextAgentTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextAgentTest.java new file mode 100755 index 000000000..7e027daba --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextAgentTest.java @@ -0,0 +1,394 @@ +/* + * 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=agent_9b91251448 +ROOST_METHOD_SIG_HASH=agent_877597f15f + +Scenario 1: Return Agent When Agent Is Set via Builder + +Details: + TestName: agentReturnsCorrectAgentWhenSetViaBuilder + Description: Verifies that the `agent()` method returns the exact `BaseAgent` instance + that was provided to the `InvocationContext` through the builder pattern. + +Execution: + Arrange: + - Create a mock of `BaseAgent`. + - Create a mock of `Session`. + - Create a mock of `BaseSessionService`. + - Create a mock of `BaseArtifactService`. + - Build an `InvocationContext` using `InvocationContext.builder()` + with `.agent(mockAgent)`, `.session(mockSession)`, + `.sessionService(mockSessionService)`, `.artifactService(mockArtifactService)`. + Act: + - Call `invocationContext.agent()` on the built context. + Assert: + - Assert that the returned value is the same instance as `mockAgent` + using `assertSame(mockAgent, invocationContext.agent())`. + +Validation: + Confirms that `agent()` correctly retrieves and returns the agent reference stored + during construction. This is fundamental to the InvocationContext's role as a + carrier of invocation state — the agent being invoked must be reliably accessible. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextAgentTest { + @Mock private BaseAgent mockAgent; + @Mock private BaseAgent anotherMockAgent; + @Mock private Session mockSession; + @Mock private BaseSessionService mockSessionService; + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private PluginManager mockPluginManager; + @Mock private RunConfig mockRunConfig; + + @BeforeEach + void setUp() { + lenient().when(mockSession.appName()).thenReturn("testApp"); + lenient().when(mockSession.userId()).thenReturn("testUser"); + } + + @Test + @Tag("valid") + void agentReturnsCorrectAgentWhenSetViaBuilder() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + assertSame(mockAgent, invocationContext.agent()); + } + + @Test + @Tag("valid") + void agentReturnsCorrectAgentWhenSetViaCreateMethod() { + InvocationContext invocationContext = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "test-invocation-id", + mockAgent, + mockSession, + null, + null); + assertSame(mockAgent, invocationContext.agent()); + } + + @Test + @Tag("valid") + void agentReturnsNullWhenNoAgentSet() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .session(mockSession) + .build(); + assertNull(invocationContext.agent()); + } + + @Test + @Tag("valid") + void agentReturnsSameInstanceAfterMutation() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + invocationContext.agent(anotherMockAgent); + assertSame(anotherMockAgent, invocationContext.agent()); + } + + @Test + @Tag("valid") + void agentReturnsSameInstanceBeforeAndAfterOtherFieldMutation() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("some-invocation-id") + .build(); + invocationContext.setEndInvocation(true); + assertSame(mockAgent, invocationContext.agent()); + } + + @Test + @Tag("valid") + void agentReturnsCorrectAgentAfterCopyOf() { + InvocationContext original = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("inv-id") + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + assertSame(mockAgent, copy.agent()); + } + + @Test + @Tag("valid") + void agentMutationDoesNotAffectCopiedContext() { + InvocationContext original = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("inv-id") + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + original.agent(anotherMockAgent); + assertSame(mockAgent, copy.agent()); + assertSame(anotherMockAgent, original.agent()); + } + + @Test + @Tag("valid") + void agentReturnsNotNullWhenAgentIsProvided() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + assertNotNull(invocationContext.agent()); + } + + @Test + @Tag("valid") + void agentReturnsSameInstanceWhenSetMultipleTimes() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + invocationContext.agent(anotherMockAgent); + invocationContext.agent(mockAgent); + assertSame(mockAgent, invocationContext.agent()); + } + + @Test + @Tag("integration") + void agentReturnsCorrectAgentInFullyConfiguredContext() { + when(mockAgent.name()).thenReturn("testAgentName"); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .agent(mockAgent) + .session(mockSession) + .invocationId("full-invocation-id") + .branch(Optional.of("branch.path")) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + BaseAgent returnedAgent = invocationContext.agent(); + assertSame(mockAgent, returnedAgent); + assertEquals("testAgentName", returnedAgent.name()); + } + + @Test + @Tag("valid") + void agentReturnsCorrectAgentWhenBuiltWithUserContent() { + Content userContent = mock(Content.class); + InvocationContext invocationContext = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "invocation-id", + mockAgent, + mockSession, + userContent, + null); + assertSame(mockAgent, invocationContext.agent()); + } + + @Test + @Tag("valid") + void agentReturnsCorrectAgentWithNullUserContent() { + InvocationContext invocationContext = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "invocation-id", + mockAgent, + mockSession, + null, + null); + assertSame(mockAgent, invocationContext.agent()); + } + + @Test + @Tag("boundary") + void agentIsNullWhenBuilderHasNoAgentSet() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .session(mockSession) + .invocationId("some-id") + .build(); + assertNull(invocationContext.agent()); + } + + @Test + @Tag("boundary") + void agentSetToNullViaSetterReturnsNull() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + invocationContext.agent(null); + assertNull(invocationContext.agent()); + } + + @Test + @Tag("valid") + void agentEqualsChecksAgentField() { + InvocationContext ctx1 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("inv-id") + .build(); + InvocationContext ctx2 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("inv-id") + .build(); + assertEquals(ctx1.agent(), ctx2.agent()); + } + + @Test + @Tag("invalid") + void agentReturnsDifferentAgentAfterSetterOverride() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + invocationContext.agent(anotherMockAgent); + assertNotSame(mockAgent, invocationContext.agent()); + assertSame(anotherMockAgent, invocationContext.agent()); + } + + @Test + @Tag("valid") + void agentReturnsCorrectAgentInDeprecatedConstructor() { + InvocationContext invocationContext = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + Optional.empty(), + Optional.empty(), + "inv-id", + mockAgent, + mockSession, + Optional.empty(), + null, + false); + assertSame(mockAgent, invocationContext.agent()); + } + + @Test + @Tag("valid") + void agentReturnsCorrectAgentInDeprecatedConstructorWithoutPluginManager() { + InvocationContext invocationContext = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + Optional.empty(), + Optional.empty(), + "inv-id", + mockAgent, + mockSession, + Optional.empty(), + null, + false); + assertSame(mockAgent, invocationContext.agent()); + } + + @Test + @Tag("integration") + void agentRemainsConsistentThroughMultipleAccesses() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("inv-id") + .build(); + BaseAgent first = invocationContext.agent(); + BaseAgent second = invocationContext.agent(); + BaseAgent third = invocationContext.agent(); + assertSame(first, second); + assertSame(second, third); + assertSame(mockAgent, first); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextAppNameTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextAppNameTest.java new file mode 100644 index 000000000..96a22966c --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextAppNameTest.java @@ -0,0 +1,550 @@ +/* + * 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=appName_a8fa25d446 +ROOST_METHOD_SIG_HASH=appName_e2ea244c93 + +Scenario 1: Return Application Name from Session + +Details: + TestName: appNameReturnsSessionAppName + Description: Verifies that the appName() method correctly delegates to the session's appName() method and returns the expected application name string. + +Execution: + Arrange: + - Create a mock Session object using Mockito. + - Configure the mock Session to return a specific non-null string value (e.g., "MyTestApp") when appName() is called. + - Build an InvocationContext using InvocationContext.builder(), setting the mock Session via .session(mockSession), and providing the minimum required fields (sessionService, artifactService, agent, session). + Act: + - Call invocationContext.appName() on the constructed InvocationContext instance. + Assert: + - Use assertEquals("MyTestApp", result) to verify the returned value matches the mocked session's appName. + +Validation: + This assertion confirms that InvocationContext.appName() correctly proxies the call to session.appName() and returns its result unchanged. This is important because InvocationContext acts as an aggregating context for a session, and the application name must consistently reflect the session's identity throughout the invocation lifecycle. + + +--- + +Scenario 2: Application Name with a Different Session App Name Value + +Details: + TestName: appNameReturnsDifferentAppNameFromSession + Description: Verifies that appName() returns the exact string value stored in the session regardless of what other fields are set in the InvocationContext, confirming no transformation is applied. + +Execution: + Arrange: + - Create a mock Session object. + - Configure the mock Session to return a distinct application name (e.g., "AnotherApplication"). + - Build an InvocationContext using InvocationContext.builder() with the mock session, along with other required fields. + Act: + - Invoke invocationContext.appName(). + Assert: + - Use assertEquals("AnotherApplication", result) to verify the exact value is returned. + +Validation: + Ensures there is no string manipulation, caching, or transformation between InvocationContext.appName() and Session.appName(). The method must return the value exactly as provided by the session. + + +--- + +Scenario 3: Application Name Delegation Verified via Session Interaction + +Details: + TestName: appNameDelegatesToSessionAppName + Description: Verifies that appName() in InvocationContext calls session.appName() exactly once, confirming proper delegation behavior. + +Execution: + Arrange: + - Create a mock Session using Mockito. + - Configure the mock Session's appName() to return "DelegationTestApp". + - Construct an InvocationContext via InvocationContext.builder() with the mock session. + Act: + - Call invocationContext.appName(). + Assert: + - Use Mockito.verify(mockSession, times(1)).appName() to confirm the session's appName() was called exactly once. + +Validation: + This verifies that InvocationContext.appName() does not bypass the session or call appName() multiple times. Ensuring single delegation is critical to avoid unintended side effects and to confirm the method's implementation is straightforward pass-through behavior. + + +--- + +Scenario 4: Application Name Consistency with Session Created via copyOf + +Details: + TestName: appNameReturnsSameValueAfterCopyOf + Description: Verifies that after creating a shallow copy of an InvocationContext using InvocationContext.copyOf(), the copied context's appName() returns the same value as the original's appName(). + +Execution: + Arrange: + - Create a mock Session configured to return "CopiedApp" for appName(). + - Build an original InvocationContext via InvocationContext.builder() with the mock session and required fields. + - Create a copy using InvocationContext.copyOf(originalContext). + Act: + - Call appName() on both the original and the copied InvocationContext. + Assert: + - Use assertEquals(originalContext.appName(), copiedContext.appName()) to verify both return the same value "CopiedApp". + +Validation: + Since copyOf() creates a shallow copy reusing the same Session object, both contexts should reference the same session and therefore return identical app names. This validates that copyOf() correctly preserves session references and that appName() consistency is maintained across copies. + + +--- + +Scenario 5: Application Name with Alphanumeric and Special Characters in App Name + +Details: + TestName: appNameReturnsAppNameWithSpecialCharacters + Description: Verifies that appName() handles and correctly returns an application name string that contains alphanumeric characters, spaces, hyphens, or underscores without any modification. + +Execution: + Arrange: + - Create a mock Session. + - Configure the mock Session to return a complex string like "My-App_v2 (Test)" when appName() is called. + - Build an InvocationContext using InvocationContext.builder() with the mock session. + Act: + - Call invocationContext.appName(). + Assert: + - Use assertEquals("My-App_v2 (Test)", result) to verify the returned string matches exactly. + +Validation: + Ensures that InvocationContext.appName() does not perform any trimming, escaping, or modification of the session's app name. The method should handle any valid string value returned by the session without alteration. + + +--- + +Scenario 6: Application Name with an Empty String Returned by Session + +Details: + TestName: appNameReturnsEmptyStringWhenSessionAppNameIsEmpty + Description: Verifies that appName() correctly returns an empty string when the underlying session's appName() returns an empty string, without throwing an exception or substituting a default value. + +Execution: + Arrange: + - Create a mock Session. + - Configure the mock Session's appName() to return an empty string "". + - Build an InvocationContext via InvocationContext.builder() with this mock session. + Act: + - Call invocationContext.appName(). + Assert: + - Use assertEquals("", result) to confirm an empty string is returned. + +Validation: + While an empty app name may be an edge case, the appName() method should faithfully return whatever the session provides without defensive logic that alters the value. This validates the absence of unintended default substitution or null-checking behavior. + + +--- + +Scenario 7: Application Name Returned via Deprecated Constructor + +Details: + TestName: appNameReturnsCorrectValueWhenBuiltWithDeprecatedConstructor + Description: Verifies that when an InvocationContext is constructed using the deprecated public constructor (not the builder), the appName() method still correctly delegates to the session's appName() method. + +Execution: + Arrange: + - Create a mock Session configured to return "DeprecatedConstructorApp" for appName(). + - Create mock instances for BaseSessionService, BaseArtifactService, BaseMemoryService, PluginManager, and BaseAgent. + - Instantiate InvocationContext using the deprecated constructor: + new InvocationContext(sessionService, artifactService, memoryService, pluginManager, Optional.empty(), Optional.empty(), "inv-id", mockAgent, mockSession, Optional.empty(), mockRunConfig, false). + Act: + - Call invocationContext.appName(). + Assert: + - Use assertEquals("DeprecatedConstructorApp", result). + +Validation: + This test confirms that the deprecated constructor correctly sets the session field and that appName() works properly regardless of which construction pathway is used. This ensures backward compatibility is maintained. + + +--- + +Scenario 8: Application Name with a Long String Value + +Details: + TestName: appNameReturnsLongAppNameString + Description: Verifies that appName() correctly handles and returns a very long application name string without truncation, corruption, or exception. + +Execution: + Arrange: + - Create a mock Session. + - Generate a long string (e.g., a 1000-character string of repeated characters) and configure the mock Session's appName() to return it. + - Build an InvocationContext using InvocationContext.builder() with this mock session. + Act: + - Call invocationContext.appName(). + Assert: + - Use assertEquals(longAppName, result) to verify the complete long string is returned unchanged. + +Validation: + Tests robustness of the delegation by ensuring that InvocationContext.appName() does not impose length restrictions or cause string-related issues when the session returns an unusually long application name. + + +--- + +Scenario 9: Application Name Is Consistent Across Multiple Calls + +Details: + TestName: appNameReturnsConsistentValueAcrossMultipleCalls + Description: Verifies that calling appName() multiple times on the same InvocationContext instance consistently returns the same value, confirming there is no state mutation or side effect between calls. + +Execution: + Arrange: + - Create a mock Session configured to return "ConsistentApp" for appName(). + - Build an InvocationContext using InvocationContext.builder() with the mock session. + Act: + - Call invocationContext.appName() three times and store each result. + Assert: + - Use assertEquals on all three returned values to confirm they are all equal to "ConsistentApp". + +Validation: + Confirms that appName() is idempotent and deterministic. Since the method simply delegates to session.appName(), repeated calls should yield identical results assuming the session's app name does not change, which is the expected behavior for the session's identity properties. + + +--- + +Scenario 10: Application Name Returned via create() Static Factory Method + +Details: + TestName: appNameReturnsCorrectValueWhenBuiltWithCreateStaticFactory + Description: Verifies that when InvocationContext is created using the static factory method InvocationContext.create(...), the appName() method correctly returns the session's application name. + +Execution: + Arrange: + - Create a mock Session configured to return "StaticFactoryApp" for appName(). + - Create mock instances for BaseSessionService, BaseArtifactService, and BaseAgent. + - Construct InvocationContext using InvocationContext.create(sessionService, artifactService, invocationId, agent, mockSession, null, runConfig). + Act: + - Call invocationContext.appName(). + Assert: + - Use assertEquals("StaticFactoryApp", result). + +Validation: + Ensures that the static factory method correctly propagates the session to the constructed InvocationContext, and that appName() reliably works regardless of which supported construction mechanism is used. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextAppNameTest { + @Mock private BaseSessionService mockSessionService; + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private PluginManager mockPluginManager; + @Mock private BaseAgent mockAgent; + @Mock private Session mockSession; + private RunConfig defaultRunConfig; + + @BeforeEach + void setUp() { + defaultRunConfig = RunConfig.builder().build(); + } + + @Test + @Tag("valid") + void appNameReturnsSessionAppName() { + when(mockSession.appName()).thenReturn("MyTestApp"); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + String result = invocationContext.appName(); + assertEquals("MyTestApp", result); + } + + @Test + @Tag("valid") + void appNameReturnsDifferentAppNameFromSession() { + when(mockSession.appName()).thenReturn("AnotherApplication"); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + String result = invocationContext.appName(); + assertEquals("AnotherApplication", result); + } + + @Test + @Tag("valid") + void appNameDelegatesToSessionAppName() { + when(mockSession.appName()).thenReturn("DelegationTestApp"); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + invocationContext.appName(); + verify(mockSession, times(1)).appName(); + } + + @Test + @Tag("integration") + void appNameReturnsSameValueAfterCopyOf() { + when(mockSession.appName()).thenReturn("CopiedApp"); + InvocationContext originalContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertEquals(originalContext.appName(), copiedContext.appName()); + assertEquals("CopiedApp", copiedContext.appName()); + } + + @Test + @Tag("valid") + void appNameReturnsAppNameWithSpecialCharacters() { + when(mockSession.appName()).thenReturn("My-App_v2 (Test)"); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + String result = invocationContext.appName(); + assertEquals("My-App_v2 (Test)", result); + } + + @Test + @Tag("boundary") + void appNameReturnsEmptyStringWhenSessionAppNameIsEmpty() { + when(mockSession.appName()).thenReturn(""); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + String result = invocationContext.appName(); + assertEquals("", result); + } + + @Test + @Tag("valid") + @SuppressWarnings("deprecation") + void appNameReturnsCorrectValueWhenBuiltWithDeprecatedConstructor() { + when(mockSession.appName()).thenReturn("DeprecatedConstructorApp"); + InvocationContext invocationContext = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + Optional.empty(), + Optional.empty(), + "inv-id", + mockAgent, + mockSession, + Optional.empty(), + defaultRunConfig, + false); + String result = invocationContext.appName(); + assertEquals("DeprecatedConstructorApp", result); + } + + @Test + @Tag("boundary") + void appNameReturnsLongAppNameString() { + String longAppName = "A".repeat(1000); + when(mockSession.appName()).thenReturn(longAppName); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + String result = invocationContext.appName(); + assertEquals(longAppName, result); + assertEquals(1000, result.length()); + } + + @Test + @Tag("valid") + void appNameReturnsConsistentValueAcrossMultipleCalls() { + when(mockSession.appName()).thenReturn("ConsistentApp"); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + String result1 = invocationContext.appName(); + String result2 = invocationContext.appName(); + String result3 = invocationContext.appName(); + assertEquals("ConsistentApp", result1); + assertEquals("ConsistentApp", result2); + assertEquals("ConsistentApp", result3); + assertEquals(result1, result2); + assertEquals(result2, result3); + } + + @Test + @Tag("valid") + void appNameReturnsCorrectValueWhenBuiltWithCreateStaticFactory() { + when(mockSession.appName()).thenReturn("StaticFactoryApp"); + InvocationContext invocationContext = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "test-invocation-id", + mockAgent, + mockSession, + null, + defaultRunConfig); + String result = invocationContext.appName(); + assertEquals("StaticFactoryApp", result); + } + + @Test + @Tag("valid") + void appNameReturnsCorrectValueWithNumericAppName() { + when(mockSession.appName()).thenReturn("App123"); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + String result = invocationContext.appName(); + assertEquals("App123", result); + } + + @Test + @Tag("integration") + void appNameReturnsSameValueAfterCopyOfVerifiesSessionCalledOnce() { + when(mockSession.appName()).thenReturn("CopiedApp"); + InvocationContext originalContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + String copiedAppName = copiedContext.appName(); + verify(mockSession, times(1)).appName(); + assertEquals("CopiedApp", copiedAppName); + } + + @Test + @Tag("valid") + @SuppressWarnings("deprecation") + void appNameReturnsCorrectValueWhenBuiltWithSecondDeprecatedConstructor() { + when(mockSession.appName()).thenReturn("DeprecatedConstructorApp2"); + InvocationContext invocationContext = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + Optional.empty(), + Optional.empty(), + "inv-id", + mockAgent, + mockSession, + Optional.empty(), + defaultRunConfig, + false); + String result = invocationContext.appName(); + assertEquals("DeprecatedConstructorApp2", result); + } + + @Test + @Tag("valid") + void appNameReturnsCorrectValueWithUnicodeCharacters() { + when(mockSession.appName()).thenReturn("App\u00E9\u00E0\u00FC"); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + String result = invocationContext.appName(); + assertEquals("App\u00E9\u00E0\u00FC", result); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextArtifactServiceTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextArtifactServiceTest.java new file mode 100755 index 000000000..98fcca30c --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextArtifactServiceTest.java @@ -0,0 +1,451 @@ +/* + * 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=artifactService_d673c8a05b +ROOST_METHOD_SIG_HASH=artifactService_769a3f8842 + +Scenario 1: Verify That artifactService Returns the Correct Non-Null Artifact Service Instance + +Details: + TestName: artifactServiceReturnsConfiguredInstance + Description: Verifies that the artifactService() method returns the exact BaseArtifactService instance + that was provided during the construction of InvocationContext via the builder. + +Execution: + Arrange: + - Create a mock or concrete implementation of BaseArtifactService. + - Create a mock or concrete implementation of BaseSessionService. + - Create a mock or concrete implementation of Session. + - Create a mock or concrete implementation of BaseAgent. + - Build an InvocationContext using InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(). + Act: + - Call invocationContext.artifactService(). + Assert: + - Assert that the returned value is not null. + - Assert that the returned value is the same instance (reference equality) as the mockArtifactService + provided to the builder. + +Validation: + This assertion confirms that the artifactService() method correctly surfaces the injected + BaseArtifactService dependency without modification or wrapping. In the context of the + InvocationContext, the artifact service is a critical dependency used for persisting artifacts + during agent execution; returning the wrong instance would cause silent failures in artifact management. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextArtifactServiceTest { + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseSessionService mockSessionService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private PluginManager mockPluginManager; + @Mock private Session mockSession; + @Mock private BaseAgent mockAgent; + @Mock private RunConfig mockRunConfig; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + when(mockSession.id()).thenReturn("testSession"); + } + + @Test + @Tag("valid") + void artifactServiceReturnsConfiguredInstance() { + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .build(); + BaseArtifactService result = context.artifactService(); + assertNotNull(result, "artifactService() should not return null"); + assertSame( + mockArtifactService, + result, + "artifactService() should return the exact same instance provided to builder"); + } + + @Test + @Tag("valid") + void artifactServiceReturnsSameReferenceAfterMultipleCalls() { + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .build(); + BaseArtifactService firstCall = context.artifactService(); + BaseArtifactService secondCall = context.artifactService(); + assertSame( + firstCall, + secondCall, + "artifactService() should return the same instance on repeated calls"); + } + + @Test + @Tag("invalid") + void artifactServiceReturnsNullWhenNotConfigured() { + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .build(); + BaseArtifactService result = context.artifactService(); + assertNull(result, "artifactService() should return null when not configured"); + } + + @Test + @Tag("valid") + void artifactServiceReturnedIsNotMemoryService() { + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .build(); + BaseArtifactService result = context.artifactService(); + assertNotNull(result); + assertSame(mockArtifactService, result); + assertNotSame( + mockMemoryService, + result, + "artifactService() should not return the memory service instance"); + } + + @Test + @Tag("valid") + void artifactServiceReturnedIsNotSessionService() { + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .build(); + BaseArtifactService result = context.artifactService(); + assertNotNull(result); + assertSame(mockArtifactService, result); + assertNotSame( + mockSessionService, + result, + "artifactService() should not return the session service instance"); + } + + @Test + @Tag("valid") + void artifactServiceIsReturnedCorrectlyWithAllDependencies() { + BaseArtifactService anotherMockArtifactService = mock(BaseArtifactService.class); + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(anotherMockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .branch(Optional.of("testBranch")) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseArtifactService result = context.artifactService(); + assertNotNull(result); + assertSame( + anotherMockArtifactService, + result, + "artifactService() should return the specific artifact service instance used in builder"); + } + + @Test + @Tag("valid") + void artifactServiceIsIndependentAcrossTwoContexts() { + BaseArtifactService firstArtifactService = mock(BaseArtifactService.class); + BaseArtifactService secondArtifactService = mock(BaseArtifactService.class); + InvocationContext firstContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(firstArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("invocation-1") + .build(); + InvocationContext secondContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(secondArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("invocation-2") + .build(); + assertSame( + firstArtifactService, + firstContext.artifactService(), + "First context should return its own artifact service"); + assertSame( + secondArtifactService, + secondContext.artifactService(), + "Second context should return its own artifact service"); + assertNotSame( + firstContext.artifactService(), + secondContext.artifactService(), + "Each context should have its own distinct artifact service"); + } + + @Test + @Tag("integration") + void artifactServiceIsPreservedAfterCopyOf() { + InvocationContext originalContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("original-invocation-id") + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull( + copiedContext.artifactService(), "Copied context should have non-null artifact service"); + assertSame( + mockArtifactService, + copiedContext.artifactService(), + "Copied context should retain the same artifact service reference"); + } + + @Test + @Tag("integration") + void artifactServiceIsPreservedAfterCopyOfWithAllFields() { + ResumabilityConfig resumabilityConfig = mock(ResumabilityConfig.class); + when(resumabilityConfig.isResumable()).thenReturn(false); + InvocationContext originalContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .agent(mockAgent) + .session(mockSession) + .invocationId("original-id") + .branch(Optional.of("main")) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(resumabilityConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertSame( + mockArtifactService, + copiedContext.artifactService(), + "CopyOf should preserve artifact service reference"); + } + + @Test + @Tag("valid") + void artifactServiceReturnedFromStaticCreateMethod() { + Content userContent = mock(Content.class); + InvocationContext context = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "test-invocation-id", + mockAgent, + mockSession, + userContent, + mockRunConfig); + assertNotNull( + context.artifactService(), + "artifactService() should not be null when using static create method"); + assertSame( + mockArtifactService, + context.artifactService(), + "artifactService() should return the exact instance passed to static create method"); + } + + @Test + @Tag("valid") + void artifactServiceReturnedFromStaticCreateMethodWithLiveRequestQueue() { + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = + InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + liveRequestQueue, + mockRunConfig); + assertNotNull( + context.artifactService(), + "artifactService() should not be null when created with live request queue"); + assertSame( + mockArtifactService, + context.artifactService(), + "artifactService() should return the exact instance used in static create with live queue"); + } + + @Test + @Tag("boundary") + void artifactServiceIsNullWhenNullExplicitlyPassedToBuilder() { + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(null) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .build(); + assertNull( + context.artifactService(), + "artifactService() should return null when explicitly set to null in builder"); + } + + @Test + @Tag("valid") + void artifactServiceDoesNotReturnPluginManager() { + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(mockPluginManager) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .build(); + BaseArtifactService result = context.artifactService(); + assertNotNull(result); + assertSame(mockArtifactService, result); + assertNotSame( + mockPluginManager, + result, + "artifactService() should not return the plugin manager instance"); + } + + @Test + @Tag("valid") + void artifactServiceIsDistinctFromSession() { + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .build(); + BaseArtifactService result = context.artifactService(); + assertNotNull(result); + assertNotSame(mockSession, result, "artifactService() should not return the session instance"); + } + + @Test + @Tag("valid") + void artifactServiceIsDistinctFromAgent() { + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .build(); + BaseArtifactService result = context.artifactService(); + assertNotNull(result); + assertNotSame(mockAgent, result, "artifactService() should not return the agent instance"); + } + + @Test + @Tag("valid") + void artifactServiceWithInvocationIdSetCorrectly() { + String invocationId = "specific-invocation-" + java.util.UUID.randomUUID(); + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId(invocationId) + .build(); + assertNotNull( + context.artifactService(), + "artifactService() should not return null with a specific invocationId"); + assertSame( + mockArtifactService, + context.artifactService(), + "artifactService() should return the same instance even with specific invocationId"); + assertEquals(invocationId, context.invocationId(), "invocationId should be correctly set"); + } + + @Test + @Tag("boundary") + void artifactServiceRemainsConsistentWhenEndInvocationIsTrue() { + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .endInvocation(true) + .build(); + assertNotNull( + context.artifactService(), + "artifactService() should not be null even when endInvocation is true"); + assertSame( + mockArtifactService, + context.artifactService(), + "artifactService() should return the correct instance even when endInvocation is true"); + assertTrue(context.endInvocation(), "endInvocation should be true"); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextBranch682Test.java b/core/src/test/java/com/google/adk/agents/InvocationContextBranch682Test.java new file mode 100755 index 000000000..42f1863df --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextBranch682Test.java @@ -0,0 +1,369 @@ +/* + * 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=branch_5cdb3cf131 +ROOST_METHOD_SIG_HASH=branch_38b042f6d4 + +Scenario 1: Return a Present Optional When Branch Is Set With a Non-Empty String + +Details: + TestName: branchReturnsPresentOptionalWhenBranchIsSetWithNonEmptyString + Description: Verifies that when the InvocationContext is built with a specific non-empty branch string, + calling branch() returns an Optional containing that exact string value. + +Execution: + Arrange: Use InvocationContext.builder() and call .branch("main-branch") to set the branch to a + non-empty string value "main-branch". Build the InvocationContext instance. + Act: Call branch() on the built InvocationContext instance. + Assert: Assert that the returned Optional is present using assertTrue(result.isPresent()), + and assert that the value equals "main-branch" using assertEquals("main-branch", result.get()). + +Validation: + This assertion verifies that the branch() method correctly wraps and returns the string value + stored in the Optional branch field. This is essential to confirm that branch information + set during construction is accurately accessible at runtime. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextBranch682Test { + @Mock private BaseSessionService mockSessionService; + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private PluginManager mockPluginManager; + @Mock private BaseAgent mockAgent; + private Session mockSession; + private RunConfig runConfig; + + @BeforeEach + void setUp() { + mockSession = + Session.builder("test-session-id").appName("test-app").userId("test-user").build(); + runConfig = RunConfig.builder().build(); + } + + private InvocationContext.Builder baseBuilder() { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .invocationId("test-invocation-id"); + } + + @Test + @Tag("valid") + void branchReturnsPresentOptionalWhenBranchIsSetWithNonEmptyString() { + // Arrange + InvocationContext context = baseBuilder().branch("main-branch").build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent()); + assertEquals("main-branch", result.get()); + } + + @Test + @Tag("valid") + void branchReturnsEmptyOptionalWhenNoBranchIsSet() { + // Arrange + InvocationContext context = baseBuilder().build(); + // Act + Optional result = context.branch(); + // Assert + assertFalse(result.isPresent()); + } + + @Test + @Tag("valid") + void branchReturnsPresentOptionalWhenBranchSetAsOptional() { + // Arrange + Optional expectedBranch = Optional.of("feature-branch"); + InvocationContext context = baseBuilder().branch(expectedBranch).build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent()); + assertEquals("feature-branch", result.get()); + } + + @Test + @Tag("valid") + void branchReturnsEmptyOptionalWhenBranchSetAsEmptyOptional() { + // Arrange + InvocationContext context = baseBuilder().branch(Optional.empty()).build(); + // Act + Optional result = context.branch(); + // Assert + assertFalse(result.isPresent()); + } + + @Test + @Tag("valid") + void branchReturnsUpdatedValueAfterMutationWithNonNullBranch() { + // Arrange + InvocationContext context = baseBuilder().branch("initial-branch").build(); + // Act + context.branch("updated-branch"); + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent()); + assertEquals("updated-branch", result.get()); + } + + @Test + @Tag("valid") + void branchReturnsEmptyOptionalAfterMutationWithNullBranch() { + // Arrange + InvocationContext context = baseBuilder().branch("some-branch").build(); + // Act + context.branch((String) null); + Optional result = context.branch(); + // Assert + assertFalse(result.isPresent()); + } + + @Test + @Tag("boundary") + void branchReturnsPresentOptionalForSingleCharacterBranchName() { + // Arrange + InvocationContext context = baseBuilder().branch("a").build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent()); + assertEquals("a", result.get()); + } + + @Test + @Tag("boundary") + void branchReturnsPresentOptionalForLongBranchName() { + // Arrange + String longBranchName = "agent_1.agent_2.agent_3.agent_4.agent_5.agent_6.agent_7.agent_8"; + InvocationContext context = baseBuilder().branch(longBranchName).build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent()); + assertEquals(longBranchName, result.get()); + } + + @Test + @Tag("boundary") + void branchReturnsPresentOptionalForBranchWithHierarchicalFormat() { + // Arrange + String hierarchicalBranch = "agentA.agentB.agentC"; + InvocationContext context = baseBuilder().branch(hierarchicalBranch).build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent()); + assertEquals(hierarchicalBranch, result.get()); + } + + @Test + @Tag("valid") + void branchReturnsCorrectValueWhenCopiedWithCopyOf() { + // Arrange + InvocationContext original = baseBuilder().branch("original-branch").build(); + // Act + InvocationContext copied = InvocationContext.copyOf(original); + Optional result = copied.branch(); + // Assert + assertTrue(result.isPresent()); + assertEquals("original-branch", result.get()); + } + + @Test + @Tag("valid") + void branchReturnsEmptyWhenCopiedFromContextWithNoBranch() { + // Arrange + InvocationContext original = baseBuilder().build(); + // Act + InvocationContext copied = InvocationContext.copyOf(original); + Optional result = copied.branch(); + // Assert + assertFalse(result.isPresent()); + } + + @Test + @Tag("integration") + void branchIsIndependentBetweenOriginalAndCopiedContext() { + // Arrange + InvocationContext original = baseBuilder().branch("shared-branch").build(); + InvocationContext copied = InvocationContext.copyOf(original); + // Act + copied.branch("modified-branch"); + Optional originalResult = original.branch(); + Optional copiedResult = copied.branch(); + // Assert + assertTrue(originalResult.isPresent()); + assertEquals("shared-branch", originalResult.get()); + assertTrue(copiedResult.isPresent()); + assertEquals("modified-branch", copiedResult.get()); + } + + @Test + @Tag("valid") + void branchReturnsCorrectValueWhenSetViaDeprecatedConstructor() { + // Arrange + Optional expectedBranch = Optional.of("deprecated-branch"); + @SuppressWarnings("deprecation") + InvocationContext context = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + Optional.empty(), + expectedBranch, + "invocation-id", + mockAgent, + mockSession, + Optional.empty(), + runConfig, + false); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent()); + assertEquals("deprecated-branch", result.get()); + } + + @Test + @Tag("valid") + void branchReturnsEmptyWhenSetToEmptyViaDeprecatedConstructor() { + // Arrange + @SuppressWarnings("deprecation") + InvocationContext context = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + Optional.empty(), + Optional.empty(), + "invocation-id", + mockAgent, + mockSession, + Optional.empty(), + runConfig, + false); + // Act + Optional result = context.branch(); + // Assert + assertFalse(result.isPresent()); + } + + @Test + @Tag("valid") + void branchReturnsCorrectValueWhenSetViaCreateMethod() { + // Arrange + Content userContent = Content.fromParts(); + InvocationContext context = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "test-invocation-id", + mockAgent, + mockSession, + userContent, + runConfig); + // Act + Optional result = context.branch(); + // Assert + assertNotNull(result); + assertFalse(result.isPresent()); + } + + @Test + @Tag("valid") + void branchReturnsPresentOptionalWhenBranchContainsSpecialCharacters() { + // Arrange + String specialBranch = "branch-with_special.chars/123"; + InvocationContext context = baseBuilder().branch(specialBranch).build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent()); + assertEquals(specialBranch, result.get()); + } + + @Test + @Tag("valid") + void branchReturnsSameOptionalTypeAfterMultipleMutations() { + // Arrange + InvocationContext context = baseBuilder().branch("initial").build(); + // Act & Assert + context.branch("first-update"); + assertTrue(context.branch().isPresent()); + assertEquals("first-update", context.branch().get()); + context.branch("second-update"); + assertTrue(context.branch().isPresent()); + assertEquals("second-update", context.branch().get()); + context.branch((String) null); + assertFalse(context.branch().isPresent()); + } + + @Test + @Tag("valid") + void branchReturnValueNotNullEvenWhenEmpty() { + // Arrange + InvocationContext context = baseBuilder().build(); + // Act + Optional result = context.branch(); + // Assert + assertNotNull(result); + } + + @Test + @Tag("valid") + void branchReturnValueNotNullWhenSet() { + // Arrange + InvocationContext context = baseBuilder().branch("test-branch").build(); + // Act + Optional result = context.branch(); + // Assert + assertNotNull(result); + assertTrue(result.isPresent()); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextBranchTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextBranchTest.java new file mode 100755 index 000000000..826ad6c32 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextBranchTest.java @@ -0,0 +1,425 @@ +/* + * 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=branch_25309d7017 +ROOST_METHOD_SIG_HASH=branch_5237016f3f + +Scenario 1: Setting a Valid Non-Null Branch String + +Details: + TestName: branchIsSetToNonNullValue + Description: Verifies that when a valid, non-null branch string is passed to the branch() method, + the internal branch field is updated to an Optional containing that string value. + +Execution: + Arrange: + - Create an InvocationContext instance using InvocationContext.builder() + with the required fields (sessionService, artifactService, session, agent, invocationId, runConfig). + - Ensure the branch is initially set to Optional.empty() or a different value via the builder. + Act: + - Call invocationContext.branch("feature-branch-001") on the created context instance. + Assert: + - Call invocationContext.branch() and assert that the returned Optional is present. + - Assert that invocationContext.branch().get() equals "feature-branch-001". + +Validation: + - This test confirms that the method correctly wraps a non-null string into an Optional and stores it + in the branch field. It ensures that a valid branch identifier representing a conversation fork + is properly persisted in the invocation context state, which is critical for branching conversation history. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextBranchTest { + @Mock private BaseSessionService mockSessionService; + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private PluginManager mockPluginManager; + @Mock private BaseAgent mockAgent; + @Mock private Session mockSession; + @Mock private RunConfig mockRunConfig; + @Mock private ResumabilityConfig mockResumabilityConfig; + private static final String APP_NAME = "testApp"; + private static final String USER_ID = "testUser"; + private static final String SESSION_ID = "testSession"; + private static final String INVOCATION_ID = "testInvocation"; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn(APP_NAME); + when(mockSession.userId()).thenReturn(USER_ID); + when(mockSession.id()).thenReturn(SESSION_ID); + } + + private InvocationContext buildDefaultContext() { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + } + + // ----------------------------------------------------------------------- + // Scenario 1: Setting a Valid Non-Null Branch String + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void branchIsSetToNonNullValue() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch("feature-branch-001"); + // Assert + assertTrue( + context.branch().isPresent(), "Branch should be present after setting a non-null value"); + assertEquals( + "feature-branch-001", context.branch().get(), "Branch value should match the set string"); + } + + // ----------------------------------------------------------------------- + // Additional Valid Scenarios + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void branchIsSetToSimpleAgentName() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch("agentA"); + // Assert + assertTrue(context.branch().isPresent()); + assertEquals("agentA", context.branch().get()); + } + + @Test + @Tag("valid") + void branchIsSetToHierarchicalAgentPath() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch("agentA.agentB.agentC"); + // Assert + assertTrue(context.branch().isPresent()); + assertEquals("agentA.agentB.agentC", context.branch().get()); + } + + @Test + @Tag("valid") + void branchCanBeOverwrittenWithDifferentValue() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch("initial-branch") + .build(); + // Act + context.branch("updated-branch"); + // Assert + assertTrue(context.branch().isPresent()); + assertEquals("updated-branch", context.branch().get()); + } + + @Test + @Tag("valid") + void branchCanBeSetAfterBuilderWithNoBranch() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Confirm initial state is empty + assertFalse(context.branch().isPresent(), "Branch should initially be empty"); + // Act + context.branch("new-branch"); + // Assert + assertTrue(context.branch().isPresent()); + assertEquals("new-branch", context.branch().get()); + } + + @Test + @Tag("valid") + void branchSetThenRetrievedMatchesExactly() { + // Arrange + InvocationContext context = buildDefaultContext(); + String branchValue = "agent_1.agent_2.agent_3"; + // Act + context.branch(branchValue); + // Assert + Optional result = context.branch(); + assertTrue(result.isPresent()); + assertSame(branchValue, result.get()); + } + + // ----------------------------------------------------------------------- + // Null / Invalid Branch Scenarios + // ----------------------------------------------------------------------- + @Test + @Tag("invalid") + void branchSetToNullResultsInEmptyOptional() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch((String) null); + // Assert + assertFalse(context.branch().isPresent(), "Branch should be empty when null is passed"); + assertEquals(Optional.empty(), context.branch()); + } + + @Test + @Tag("invalid") + void branchSetToNullAfterNonNullValueClearsIt() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch("some-branch") + .build(); + assertTrue(context.branch().isPresent(), "Branch should be present before setting null"); + // Act + context.branch((String) null); + // Assert + assertFalse(context.branch().isPresent(), "Branch should be empty after setting null"); + } + + // ----------------------------------------------------------------------- + // Boundary Scenarios + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void branchSetToEmptyString() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch(""); + // Assert + assertTrue(context.branch().isPresent(), "Branch Optional should be present for empty string"); + assertEquals("", context.branch().get(), "Branch value should be an empty string"); + } + + @Test + @Tag("boundary") + void branchSetToSingleCharacter() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch("a"); + // Assert + assertTrue(context.branch().isPresent()); + assertEquals("a", context.branch().get()); + } + + @Test + @Tag("boundary") + void branchSetToVeryLongString() { + // Arrange + InvocationContext context = buildDefaultContext(); + String longBranch = "agent".repeat(200); + // Act + context.branch(longBranch); + // Assert + assertTrue(context.branch().isPresent()); + assertEquals(longBranch, context.branch().get()); + } + + @Test + @Tag("boundary") + void branchSetToStringWithSpecialCharacters() { + // Arrange + InvocationContext context = buildDefaultContext(); + String specialBranch = "agent-1_sub.agent-2_sub"; + // Act + context.branch(specialBranch); + // Assert + assertTrue(context.branch().isPresent()); + assertEquals(specialBranch, context.branch().get()); + } + + @Test + @Tag("boundary") + void branchSetToWhitespaceOnlyString() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch(" "); + // Assert + assertTrue( + context.branch().isPresent(), "Branch Optional should be present for whitespace string"); + assertEquals(" ", context.branch().get()); + } + + // ----------------------------------------------------------------------- + // Integration Scenarios + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void branchSetOnContextCreatedWithCreateMethod() { + // Arrange + Content userContent = mock(Content.class); + InvocationContext context = + InvocationContext.create( + mockSessionService, + mockArtifactService, + INVOCATION_ID, + mockAgent, + mockSession, + userContent, + mockRunConfig); + assertFalse(context.branch().isPresent(), "Branch should initially be absent"); + // Act + context.branch("feature-branch-001"); + // Assert + assertTrue(context.branch().isPresent()); + assertEquals("feature-branch-001", context.branch().get()); + } + + @Test + @Tag("integration") + void branchSetOnCopiedContextDoesNotAffectOriginal() { + // Arrange + InvocationContext original = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch("original-branch") + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + // Act + copy.branch("copied-branch"); + // Assert + assertEquals( + "original-branch", original.branch().get(), "Original branch should remain unchanged"); + assertEquals("copied-branch", copy.branch().get(), "Copy branch should be updated"); + } + + @Test + @Tag("integration") + void branchSetOnCopiedContextIndependently() { + // Arrange + InvocationContext original = buildDefaultContext(); + InvocationContext copy = InvocationContext.copyOf(original); + // Act + original.branch("branch-original"); + copy.branch("branch-copy"); + // Assert + assertEquals("branch-original", original.branch().get()); + assertEquals("branch-copy", copy.branch().get()); + } + + @Test + @Tag("integration") + void branchReturnedIsWrappedInOptionalOfNullable() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act: set non-null + context.branch("my-branch"); + Optional presentOptional = context.branch(); + // Assert non-null + assertNotNull(presentOptional); + assertTrue(presentOptional.isPresent()); + assertEquals("my-branch", presentOptional.get()); + // Act: set null + context.branch((String) null); + Optional emptyOptional = context.branch(); + // Assert null + assertNotNull(emptyOptional, "Optional itself should not be null even when value is null"); + assertFalse(emptyOptional.isPresent(), "Optional should be empty when branch set to null"); + } + + @Test + @Tag("integration") + void branchCanBeSetMultipleTimesSequentially() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act & Assert sequence + context.branch("step-1"); + assertEquals("step-1", context.branch().orElse(null)); + context.branch("step-2"); + assertEquals("step-2", context.branch().orElse(null)); + context.branch((String) null); + assertFalse(context.branch().isPresent()); + context.branch("step-3"); + assertEquals("step-3", context.branch().orElse(null)); + } + + @Test + @Tag("integration") + void branchBuiltViaBuilderBranchMethodMatchesBranchSetter() { + // Arrange + String branchValue = "agentX.agentY"; + InvocationContext contextViaBuilder = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(branchValue) + .build(); + InvocationContext contextViaSetter = buildDefaultContext(); + contextViaSetter.branch(branchValue); + // Assert + assertEquals( + contextViaBuilder.branch(), + contextViaSetter.branch(), + "Branch set via builder and via setter should produce the same Optional"); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextBuilderTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextBuilderTest.java new file mode 100755 index 000000000..4d6b3d319 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextBuilderTest.java @@ -0,0 +1,734 @@ +/* + * 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=builder_2187989d2f +ROOST_METHOD_SIG_HASH=builder_80041265ae + +Scenario 1: Verify That builder() Returns a Non-Null Builder Instance + +Details: + TestName: builderReturnsNonNullInstance + Description: Verifies that the static builder() method returns a non-null instance of InvocationContext.Builder, + confirming that the factory method is correctly implemented and usable. +Execution: + Arrange: No special setup is required since builder() is a static factory method with no dependencies. + Act: Call InvocationContext.builder() and capture the returned object. + Assert: Use assertNotNull to confirm the returned Builder instance is not null. +Validation: + The assertion confirms that calling builder() always produces a valid, non-null Builder object. + This is fundamental to the Builder pattern; if the factory method returned null, all subsequent + chaining calls would throw a NullPointerException, making the entire construction API unusable. + + +Scenario 2: Verify That builder() Returns an Instance of the Correct Type + +Details: + TestName: builderReturnsCorrectBuilderType + Description: Verifies that the object returned by builder() is specifically an instance of + InvocationContext.Builder, ensuring the return type is correct and not some other class. +Execution: + Arrange: No special setup is required. + Act: Call InvocationContext.builder() and capture the returned object. + Assert: Use assertInstanceOf (or assertTrue with instanceof) to verify the returned object + is an instance of InvocationContext.Builder. +Validation: + This test ensures type safety: the method contract states it returns a Builder, so the runtime + type must match. Returning an incorrect type would break all downstream usages that rely on + Builder-specific methods. + + +Scenario 3: Verify That Each Call to builder() Returns a New, Distinct Builder Instance + +Details: + TestName: builderReturnsNewInstanceOnEachCall + Description: Verifies that multiple successive calls to builder() return different (non-identical) + Builder instances, confirming that no singleton or cached object is being returned. +Execution: + Arrange: No special setup is required. + Act: Call InvocationContext.builder() twice, capturing the result of each call as builder1 and builder2. + Assert: Use assertNotSame to confirm builder1 and builder2 are not the same object reference. +Validation: + Builder instances must be independent to prevent shared mutable state between different + InvocationContext construction flows. If both calls returned the same object, concurrent or + sequential builds could interfere with each other, leading to incorrect InvocationContext + configurations. + + +Scenario 4: Verify That the Builder Produced by builder() Can Successfully Build an InvocationContext + +Details: + TestName: builderCanBuildInvocationContextSuccessfully + Description: Verifies that the Builder returned by builder() is fully functional and can produce + a valid InvocationContext instance when build() is called after configuring required fields. +Execution: + Arrange: Create mock or stub implementations of BaseSessionService, BaseArtifactService, BaseAgent, + Session, and RunConfig. Prepare an invocationId string. + Act: Call InvocationContext.builder(), chain the necessary setter methods + (sessionService, artifactService, agent, session, runConfig, invocationId), and call build(). + Assert: Use assertNotNull to verify the returned InvocationContext instance is not null. +Validation: + This end-to-end test confirms that builder() provides a Builder that is fully operational. + A Builder returned by the factory method that cannot complete a build would render the entire + construction API broken, directly impacting the ability to create InvocationContext objects + throughout the application. + + +Scenario 5: Verify That the Builder Returned by builder() Has Default endInvocation Value of False + +Details: + TestName: builderDefaultEndInvocationIsFalse + Description: Verifies that the Builder created by builder() initializes the endInvocation field + to false by default, so that a built InvocationContext reflects this default state. +Execution: + Arrange: Create the minimum required mocks/stubs for BaseSessionService, BaseArtifactService, + BaseAgent, and Session to allow a valid build. + Act: Call InvocationContext.builder(), set required fields, and call build() without explicitly + setting endInvocation. Capture the resulting InvocationContext. + Assert: Call endInvocation() on the resulting context and assert it equals false. +Validation: + The Builder class defines endInvocation with a default value of false. This test confirms that + the default is correctly propagated from builder() through the build process. An incorrect default + could cause invocations to terminate prematurely without any explicit instruction to do so. + + +Scenario 6: Verify That the Builder Returned by builder() Has a Default Non-Null invocationId + +Details: + TestName: builderDefaultInvocationIdIsNotNull + Description: Verifies that the Builder created by builder() automatically generates a non-null, + non-empty invocationId by default (using newInvocationContextId()), so the built + InvocationContext has a valid ID without requiring explicit setting. +Execution: + Arrange: Create the minimum required mocks/stubs for BaseSessionService, BaseArtifactService, + BaseAgent, and Session. + Act: Call InvocationContext.builder(), set the required fields without calling invocationId(), + and call build(). Capture the resulting InvocationContext. + Assert: Call invocationId() on the resulting context and assert it is not null and not empty. +Validation: + The Builder's invocationId field is pre-initialized with newInvocationContextId(), which generates + a UUID-based string. This guarantees every InvocationContext has a unique ID by default, which is + critical for tracking and correlating invocations across the system. + + +Scenario 7: Verify That the Builder Returned by builder() Has a Default Empty liveRequestQueue + +Details: + TestName: builderDefaultLiveRequestQueueIsEmpty + Description: Verifies that the Builder created by builder() initializes the liveRequestQueue + field to Optional.empty() by default, so a built InvocationContext reflects this + when no live queue is set. +Execution: + Arrange: Create the minimum required mocks/stubs for BaseSessionService, BaseArtifactService, + BaseAgent, and Session. + Act: Call InvocationContext.builder(), set required fields without calling liveRequestQueue(), + and call build(). Capture the resulting InvocationContext. + Assert: Call liveRequestQueue() on the resulting context and assert it equals Optional.empty(). +Validation: + The default empty Optional for liveRequestQueue reflects the common case where no live + streaming queue is needed. This test ensures that the builder() factory method produces a + Builder that correctly defaults this optional field, preventing unexpected non-empty values + for standard (non-live) invocations. + + +Scenario 8: Verify That the Builder Returned by builder() Has a Default Empty branch + +Details: + TestName: builderDefaultBranchIsEmpty + Description: Verifies that the Builder created by builder() initializes the branch field + to Optional.empty() by default, so a built InvocationContext has no branch + unless explicitly set. +Execution: + Arrange: Create the minimum required mocks/stubs for BaseSessionService, BaseArtifactService, + BaseAgent, and Session. + Act: Call InvocationContext.builder(), set required fields without calling branch(), + and call build(). Capture the resulting InvocationContext. + Assert: Call branch() on the resulting context and assert it equals Optional.empty(). +Validation: + The branch field represents a fork in conversation history and defaults to empty when no + specific branch is needed. This test confirms the factory method produces a correctly + defaulted Builder for this field, ensuring that uninitiated invocations do not carry + spurious branch identifiers. + + +Scenario 9: Verify That the Builder Returned by builder() Has a Default Empty userContent + +Details: + TestName: builderDefaultUserContentIsEmpty + Description: Verifies that the Builder created by builder() initializes the userContent field + to Optional.empty() by default, so a built InvocationContext has no user content + unless explicitly set. +Execution: + Arrange: Create the minimum required mocks/stubs for BaseSessionService, BaseArtifactService, + BaseAgent, and Session. + Act: Call InvocationContext.builder(), set required fields without calling userContent(), + and call build(). Capture the resulting InvocationContext. + Assert: Call userContent() on the resulting context and assert it equals Optional.empty(). +Validation: + Not all invocations are triggered by explicit user content (e.g., programmatic or scheduled + invocations). The empty default ensures that the absence of user input is clearly represented + as Optional.empty() rather than null, promoting safer Optional-based handling downstream. + + +Scenario 10: Verify That the Builder Returned by builder() Supports Method Chaining + +Details: + TestName: builderSupportsMethodChaining + Description: Verifies that the Builder returned by builder() supports fluent method chaining, + i.e., each setter method returns the same Builder instance, enabling all fields + to be set in a single chained expression. +Execution: + Arrange: Create mock or stub implementations of BaseSessionService, BaseArtifactService, + BaseAgent, and Session. + Act: Call InvocationContext.builder() and chain multiple setter calls (sessionService, + artifactService, agent, session), capturing the builder reference returned from each call. + Assert: Use assertSame to confirm the Builder returned after each setter call is the same object + as the one returned by builder(). +Validation: + Fluent method chaining is a core feature of the Builder pattern. If any setter returned a + different object or null, the chain would break, forcing users to call each setter separately + on a stored reference. This test ensures the Builder API is ergonomic and consistent. + + +Scenario 11: Verify That Two Builders From builder() Are Independent (State Isolation) + +Details: + TestName: twoBuilderInstancesAreStateIndependent + Description: Verifies that configuring one Builder instance returned by builder() does not + affect another Builder instance returned by a separate call to builder(), confirming + full state isolation between the two. +Execution: + Arrange: Create mock/stub implementations for BaseSessionService, BaseArtifactService, + BaseAgent, and Session. Prepare two distinct invocationId strings: "invocation-1" + and "invocation-2". + Act: Call InvocationContext.builder() twice to get builder1 and builder2. Set invocationId + to "invocation-1" on builder1 and invocationId to "invocation-2" on builder2. + Build both to get context1 and context2. + Assert: Assert that context1.invocationId() equals "invocation-1" and context2.invocationId() + equals "invocation-2", confirming no cross-contamination. +Validation: + Independent Builder instances are essential for creating multiple distinct InvocationContext + objects within the same execution scope. If builders shared state (e.g., via static fields), + configurations from one build would corrupt another, causing incorrect behavior in concurrent + or sequential invocation scenarios. + + +Scenario 12: Verify That the Builder Returned by builder() Has a Non-Null Default PluginManager + +Details: + TestName: builderDefaultPluginManagerIsNotNull + Description: Verifies that the Builder created by builder() initializes pluginManager to a + non-null default instance (new PluginManager()), so a built InvocationContext + always has a valid plugin manager even if none is explicitly provided. +Execution: + Arrange: Create the minimum required mocks/stubs for BaseSessionService, BaseArtifactService, + BaseAgent, and Session. + Act: Call InvocationContext.builder(), set required fields without calling pluginManager(), + and call build(). Capture the resulting InvocationContext. + Assert: Call pluginManager() on the resulting context and assert it is not null. +Validation: + A null PluginManager would cause NullPointerExceptions anywhere the agent attempts to + access tools or plugins. By defaulting to a new PluginManager(), the builder() factory + ensures a safe, non-null baseline configuration for every InvocationContext without + requiring explicit initialization by the caller. + + +Scenario 13: Verify That the Builder Returned by builder() Has a Non-Null Default RunConfig + +Details: + TestName: builderDefaultRunConfigIsNotNull + Description: Verifies that the Builder created by builder() initializes runConfig to a + non-null default instance (RunConfig.builder().build()), so a built InvocationContext + always has a valid run configuration even if none is explicitly provided. +Execution: + Arrange: Create the minimum required mocks/stubs for BaseSessionService, BaseArtifactService, + BaseAgent, and Session. + Act: Call InvocationContext.builder(), set required fields without calling runConfig(), + and call build(). Capture the resulting InvocationContext. + Assert: Call runConfig() on the resulting context and assert it is not null. +Validation: + RunConfig controls critical runtime parameters like maxLlmCalls. A null RunConfig would + cause NullPointerExceptions in incrementLlmCallsCount() and other config-dependent logic. + The non-null default ensures that every InvocationContext is safely operational out of the box. + + +Scenario 14: Verify That the Default invocationId Generated by builder() Starts With the Expected Prefix + +Details: + TestName: builderDefaultInvocationIdStartsWithExpectedPrefix + Description: Verifies that the default invocationId generated in a new Builder (via + newInvocationContextId()) starts with the "e-" prefix, conforming to the + defined ID format. +Execution: + Arrange: Create the minimum required mocks/stubs for BaseSessionService, BaseArtifactService, + BaseAgent, and Session. + Act: Call InvocationContext.builder(), set required fields without calling invocationId(), + and call build(). Retrieve the invocationId from the resulting InvocationContext. + Assert: Use assertTrue with String.startsWith("e-") to verify the generated ID conforms to + the expected format. +Validation: + The newInvocationContextId() method produces IDs in the format "e-". Verifying the + prefix ensures the ID generation logic is correctly invoked by the Builder when builder() + is called, maintaining consistency in invocation ID formats used across logging, tracing, + and event correlation. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.genai.types.FunctionCall; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; + +@ExtendWith(MockitoExtension.class) +class InvocationContextBuilderTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private BaseAgent mockAgent; + @Mock + private Session mockSession; + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + } + @Test + @Tag("valid") + void builderReturnsNonNullInstance() { + // Act + InvocationContext.Builder builder = InvocationContext.builder(); + // Assert + assertNotNull(builder, "builder() should return a non-null Builder instance"); + } + @Test + @Tag("valid") + void builderReturnsCorrectBuilderType() { + // Act + Object builder = InvocationContext.builder(); + // Assert + assertInstanceOf( + InvocationContext.Builder.class, + builder, + "builder() should return an instance of InvocationContext.Builder"); + } + @Test + @Tag("valid") + void builderReturnsNewInstanceOnEachCall() { + // Act + InvocationContext.Builder builder1 = InvocationContext.builder(); + InvocationContext.Builder builder2 = InvocationContext.builder(); + // Assert + assertNotSame( + builder1, + builder2, + "Each call to builder() should return a distinct Builder instance"); + } + @Test + @Tag("integration") + void builderCanBuildInvocationContextSuccessfully() { + // Arrange + String invocationId = "e-test-invocation-id"; + // Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(invocationId) + .agent(mockAgent) + .session(mockSession) + .build(); + // Assert + assertNotNull(context, "build() should return a non-null InvocationContext instance"); + } + @Test + @Tag("valid") + void builderDefaultEndInvocationIsFalse() { + // Arrange & Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + // Assert + assertFalse( + context.endInvocation(), + "Default endInvocation should be false in a freshly built InvocationContext"); + } + @Test + @Tag("valid") + void builderDefaultInvocationIdIsNotNull() { + // Arrange & Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + // Assert + assertNotNull( + context.invocationId(), + "Default invocationId should be non-null"); + assertFalse( + context.invocationId().isEmpty(), + "Default invocationId should be non-empty"); + } + @Test + @Tag("valid") + void builderDefaultLiveRequestQueueIsEmpty() { + // Arrange & Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + // Assert + assertEquals( + Optional.empty(), + context.liveRequestQueue(), + "Default liveRequestQueue should be Optional.empty()"); + } + @Test + @Tag("valid") + void builderDefaultBranchIsEmpty() { + // Arrange & Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + // Assert + assertEquals( + Optional.empty(), + context.branch(), + "Default branch should be Optional.empty()"); + } + @Test + @Tag("valid") + void builderDefaultUserContentIsEmpty() { + // Arrange & Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + // Assert + assertEquals( + Optional.empty(), + context.userContent(), + "Default userContent should be Optional.empty()"); + } + @Test + @Tag("valid") + void builderSupportsMethodChaining() { + // Arrange + InvocationContext.Builder builder = InvocationContext.builder(); + // Act + InvocationContext.Builder afterSessionService = builder.sessionService(mockSessionService); + InvocationContext.Builder afterArtifactService = + afterSessionService.artifactService(mockArtifactService); + InvocationContext.Builder afterAgent = afterArtifactService.agent(mockAgent); + InvocationContext.Builder afterSession = afterAgent.session(mockSession); + // Assert + assertSame( + builder, + afterSessionService, + "sessionService() should return the same Builder instance"); + assertSame( + builder, + afterArtifactService, + "artifactService() should return the same Builder instance"); + assertSame(builder, afterAgent, "agent() should return the same Builder instance"); + assertSame(builder, afterSession, "session() should return the same Builder instance"); + } + @Test + @Tag("valid") + void twoBuilderInstancesAreStateIndependent() { + // Arrange + String invocationId1 = "invocation-1"; + String invocationId2 = "invocation-2"; + // Act + InvocationContext.Builder builder1 = InvocationContext.builder(); + InvocationContext.Builder builder2 = InvocationContext.builder(); + InvocationContext context1 = + builder1 + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId(invocationId1) + .build(); + InvocationContext context2 = + builder2 + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId(invocationId2) + .build(); + // Assert + assertEquals( + invocationId1, + context1.invocationId(), + "context1 should have invocationId 'invocation-1'"); + assertEquals( + invocationId2, + context2.invocationId(), + "context2 should have invocationId 'invocation-2'"); + assertNotEquals( + context1.invocationId(), + context2.invocationId(), + "Two separately built contexts should have different invocationIds"); + } + @Test + @Tag("valid") + void builderDefaultPluginManagerIsNotNull() { + // Arrange & Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + // Assert + assertNotNull( + context.pluginManager(), + "Default pluginManager should be non-null"); + } + @Test + @Tag("valid") + void builderDefaultRunConfigIsNotNull() { + // Arrange & Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + // Assert + assertNotNull( + context.runConfig(), + "Default runConfig should be non-null"); + } + @Test + @Tag("valid") + void builderDefaultInvocationIdStartsWithExpectedPrefix() { + // Arrange & Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + String invocationId = context.invocationId(); + // Assert + assertNotNull(invocationId, "Default invocationId should not be null"); + assertTrue( + invocationId.startsWith("e-"), + "Default invocationId should start with 'e-', but was: " + invocationId); + } + @Test + @Tag("boundary") + void builderWithExplicitlySetInvocationIdOverridesDefault() { + // Arrange + String customInvocationId = "custom-invocation-id-123"; + // Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId(customInvocationId) + .build(); + // Assert + assertEquals( + customInvocationId, + context.invocationId(), + "Explicitly set invocationId should override the default"); + } + @Test + @Tag("valid") + void builderWithAllFieldsSetBuildsCorrectly() { + // Arrange + String invocationId = "e-full-context-id"; + String branch = "agentA.agentB"; + PluginManager pluginManager = new PluginManager(java.util.List.of()); + // Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(pluginManager) + .agent(mockAgent) + .session(mockSession) + .invocationId(invocationId) + .branch(branch) + .endInvocation(false) + .build(); + // Assert + assertNotNull(context); + assertEquals(invocationId, context.invocationId()); + assertEquals(Optional.of(branch), context.branch()); + assertFalse(context.endInvocation()); + assertEquals(mockSessionService, context.sessionService()); + assertEquals(mockArtifactService, context.artifactService()); + assertEquals(mockMemoryService, context.memoryService()); + assertEquals(pluginManager, context.pluginManager()); + assertEquals(mockAgent, context.agent()); + assertEquals(mockSession, context.session()); + } + @Test + @Tag("valid") + void builderWithEndInvocationSetToTrue() { + // Arrange & Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .endInvocation(true) + .build(); + // Assert + assertTrue( + context.endInvocation(), + "endInvocation should be true when explicitly set to true"); + } + @Test + @Tag("integration") + void builderCanCreateMultipleDistinctContextsSequentially() { + // Arrange + String invocationId1 = "e-context-one"; + String invocationId2 = "e-context-two"; + // Act + InvocationContext context1 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId(invocationId1) + .build(); + InvocationContext context2 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId(invocationId2) + .build(); + // Assert + assertNotNull(context1); + assertNotNull(context2); + assertNotSame(context1, context2, "Each build() call should produce a distinct object"); + assertEquals(invocationId1, context1.invocationId()); + assertEquals(invocationId2, context2.invocationId()); + } + @Test + @Tag("valid") + void builderDefaultInvocationIdIsUniqueAcrossTwoCalls() { + // Arrange & Act + InvocationContext context1 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + InvocationContext context2 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + // Assert + assertNotEquals( + context1.invocationId(), + context2.invocationId(), + "Each Builder should generate a unique default invocationId"); + } + @Test + @Tag("valid") + void builderWithBranchSetAsOptional() { + // Arrange + Optional branchOpt = Optional.of("agentRoot.agentChild"); + // Act + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .branch(branchOpt) + .build(); + // Assert + assertEquals(branchOpt, context.branch(), "Branch should match the provided Optional"); + } + @Test + @Tag("boundary") + void builderWithEmptyBranchOptional() { + // Arrange & Act + InvocationContext context = + InvocationContext.builder() + .session \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextCopyOfTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextCopyOfTest.java new file mode 100755 index 000000000..1f57b9ff4 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextCopyOfTest.java @@ -0,0 +1,448 @@ +/* + * 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=copyOf_49f0784c6e +ROOST_METHOD_SIG_HASH=copyOf_2c012f0957 + +Scenario 1: Copy of a fully populated InvocationContext produces an equal new context + +Details: + TestName: copyOfFullyPopulatedContextProducesEqualNewContext + Description: Verifies that when copyOf is called with a fully populated InvocationContext + (all fields set), the resulting new context is equal to the original based + on the equals() contract defined in the class. + +Execution: + Arrange: + - Create a mock or concrete instance of BaseSessionService. + - Create a mock or concrete instance of BaseArtifactService. + - Create a mock or concrete instance of BaseMemoryService. + - Create a concrete PluginManager instance. + - Create a mock Session instance with appName and userId. + - Create a Content instance for userContent. + - Create a RunConfig instance using RunConfig.builder().build(). + - Create a ResumabilityConfig instance. + - Create a LiveRequestQueue instance. + - Build the original InvocationContext using InvocationContext.builder() with all + fields set: sessionService, artifactService, memoryService, pluginManager, + liveRequestQueue, branch("main-branch"), invocationId("test-invocation-id"), + agent, session, userContent, runConfig, endInvocation(false), resumabilityConfig. + Act: + - Call InvocationContext.copyOf(originalContext). + Assert: + - Assert that the returned context is not null. + - Assert that the returned context equals the original using assertEquals(original, copy). + +Validation: + Verifies that copyOf correctly transfers all field values to the new InvocationContext + and that the equals() method confirms structural equality. This is fundamental to + ensure that the copy operation is lossless and produces a logically equivalent context. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.ArrayList; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.genai.types.FunctionCall; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nullable; + +@ExtendWith(MockitoExtension.class) +class InvocationContextCopyOfTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private BaseAgent mockAgent; + @Mock + private Session mockSession; + @Mock + private RunConfig mockRunConfig; + @Mock + private ResumabilityConfig mockResumabilityConfig; + private PluginManager pluginManager; + @BeforeEach + void setUp() { + pluginManager = new PluginManager(new ArrayList<>()); + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + } + @Test + @Tag("valid") + void copyOfFullyPopulatedContextProducesEqualNewContext() { + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + Content userContent = Content.builder().role("user").build(); + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(pluginManager) + .liveRequestQueue(Optional.of(liveRequestQueue)) + .branch(Optional.of("main-branch")) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(userContent)) + .runConfig(mockRunConfig) + .endInvocation(false) + .resumabilityConfig(mockResumabilityConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertEquals(originalContext, copiedContext); + } + @Test + @Tag("valid") + void copyOfPreservesSessionService() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertSame(mockSessionService, copiedContext.sessionService()); + } + @Test + @Tag("valid") + void copyOfPreservesArtifactService() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertSame(mockArtifactService, copiedContext.artifactService()); + } + @Test + @Tag("valid") + void copyOfPreservesMemoryService() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertSame(mockMemoryService, copiedContext.memoryService()); + } + @Test + @Tag("valid") + void copyOfPreservesPluginManager() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(pluginManager) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertSame(pluginManager, copiedContext.pluginManager()); + } + @Test + @Tag("valid") + void copyOfPreservesInvocationId() { + String expectedInvocationId = "test-invocation-id-123"; + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertEquals(expectedInvocationId, copiedContext.invocationId()); + } + @Test + @Tag("valid") + void copyOfPreservesBranch() { + String expectedBranch = "agent1.agent2.agent3"; + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(Optional.of(expectedBranch)) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertTrue(copiedContext.branch().isPresent()); + assertEquals(expectedBranch, copiedContext.branch().get()); + } + @Test + @Tag("valid") + void copyOfPreservesEmptyBranch() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(Optional.empty()) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertFalse(copiedContext.branch().isPresent()); + } + @Test + @Tag("valid") + void copyOfPreservesAgent() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertSame(mockAgent, copiedContext.agent()); + } + @Test + @Tag("valid") + void copyOfPreservesSession() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertSame(mockSession, copiedContext.session()); + } + @Test + @Tag("valid") + void copyOfPreservesUserContent() { + Content userContent = Content.builder().role("user").build(); + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(userContent)) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertTrue(copiedContext.userContent().isPresent()); + assertSame(userContent, copiedContext.userContent().get()); + } + @Test + @Tag("valid") + void copyOfPreservesEmptyUserContent() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertFalse(copiedContext.userContent().isPresent()); + } + @Test + @Tag("valid") + void copyOfPreservesRunConfig() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertSame(mockRunConfig, copiedContext.runConfig()); + } + @Test + @Tag("valid") + void copyOfPreservesEndInvocationTrue() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .endInvocation(true) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertTrue(copiedContext.endInvocation()); + } + @Test + @Tag("valid") + void copyOfPreservesEndInvocationFalse() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertFalse(copiedContext.endInvocation()); + } + @Test + @Tag("valid") + void copyOfPreservesResumabilityConfig() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertSame(mockResumabilityConfig, copiedContext.resumabilityConfig()); + } + @Test + @Tag("valid") + void copyOfPreservesLiveRequestQueue() { + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .liveRequestQueue(Optional.of(liveRequestQueue)) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertTrue(copiedContext.liveRequestQueue().isPresent()); + assertSame(liveRequestQueue, copiedContext.liveRequestQueue().get()); + } + @Test + @Tag("valid") + void copyOfPreservesEmptyLiveRequestQueue() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .liveRequestQueue(Optional.empty()) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertFalse(copiedContext.liveRequestQueue().isPresent()); + } + @Test + @Tag("valid") + void copyOfProducesDistinctObjectInstance() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNotNull(copiedContext); + assertNotSame(originalContext, copiedContext); + } + @Test + @Tag("valid") + void copyOfCopiesActiveStreamingTools() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSession \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextCreate563Test.java b/core/src/test/java/com/google/adk/agents/InvocationContextCreate563Test.java new file mode 100755 index 000000000..83b669b37 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextCreate563Test.java @@ -0,0 +1,521 @@ +/* + * 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=create_6e6963ca78 +ROOST_METHOD_SIG_HASH=create_ac1179ff57 + +public static InvocationContext create(BaseSessionService sessionService, BaseArtifactService artifactService, BaseAgent agent, Session session, LiveRequestQueue liveRequestQueue, RunConfig runConfig) + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.genai.types.FunctionCall; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; + +@ExtendWith(MockitoExtension.class) +class InvocationContextCreate563Test { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseAgent mockAgent; + @Mock + private Session mockSession; + @Mock + private LiveRequestQueue mockLiveRequestQueue; + @Mock + private RunConfig mockRunConfig; + private static final String TEST_APP_NAME = "testApp"; + private static final String TEST_USER_ID = "testUser"; + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn(TEST_APP_NAME); + when(mockSession.userId()).thenReturn(TEST_USER_ID); + } + @Test + @Tag("valid") + void create_withAllValidParameters_returnsNonNullContext() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNotNull(context); + } + @Test + @Tag("valid") + void create_withAllValidParameters_sessionServiceIsSet() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertSame(mockSessionService, context.sessionService()); + } + @Test + @Tag("valid") + void create_withAllValidParameters_artifactServiceIsSet() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertSame(mockArtifactService, context.artifactService()); + } + @Test + @Tag("valid") + void create_withAllValidParameters_agentIsSet() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertSame(mockAgent, context.agent()); + } + @Test + @Tag("valid") + void create_withAllValidParameters_sessionIsSet() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertSame(mockSession, context.session()); + } + @Test + @Tag("valid") + void create_withAllValidParameters_liveRequestQueueIsPresent() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertTrue(context.liveRequestQueue().isPresent()); + assertSame(mockLiveRequestQueue, context.liveRequestQueue().get()); + } + @Test + @Tag("valid") + void create_withAllValidParameters_runConfigIsSet() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertSame(mockRunConfig, context.runConfig()); + } + @Test + @Tag("valid") + void create_withNullLiveRequestQueue_liveRequestQueueIsEmpty() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + null, + mockRunConfig + ); + assertNotNull(context); + assertFalse(context.liveRequestQueue().isPresent()); + } + @Test + @Tag("valid") + void create_withNullLiveRequestQueue_otherFieldsAreSet() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + null, + mockRunConfig + ); + assertSame(mockSessionService, context.sessionService()); + assertSame(mockArtifactService, context.artifactService()); + assertSame(mockAgent, context.agent()); + assertSame(mockSession, context.session()); + assertSame(mockRunConfig, context.runConfig()); + } + @Test + @Tag("valid") + void create_withNullSessionService_returnsContextWithNullSessionService() { + InvocationContext context = InvocationContext.create( + null, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNotNull(context); + assertNull(context.sessionService()); + } + @Test + @Tag("valid") + void create_withNullArtifactService_returnsContextWithNullArtifactService() { + InvocationContext context = InvocationContext.create( + mockSessionService, + null, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNotNull(context); + assertNull(context.artifactService()); + } + @Test + @Tag("valid") + void create_withNullRunConfig_returnsContextWithNullRunConfig() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + null + ); + assertNotNull(context); + assertNull(context.runConfig()); + } + @Test + @Tag("valid") + void create_invocationIdIsNotSetByThisOverload_invocationIdIsNull() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNotNull(context); + assertNull(context.invocationId()); + } + @Test + @Tag("valid") + void create_userContentIsNotSetByThisOverload_userContentIsEmpty() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNotNull(context); + assertFalse(context.userContent().isPresent()); + } + @Test + @Tag("valid") + void create_branchIsNotSet_branchIsEmpty() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNotNull(context); + assertFalse(context.branch().isPresent()); + } + @Test + @Tag("valid") + void create_endInvocationDefaultIsFalse() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNotNull(context); + assertFalse(context.endInvocation()); + } + @Test + @Tag("valid") + void create_appNameDelegatesFromSession() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertEquals(TEST_APP_NAME, context.appName()); + } + @Test + @Tag("valid") + void create_userIdDelegatesFromSession() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertEquals(TEST_USER_ID, context.userId()); + } + @Test + @Tag("valid") + void create_activeStreamingToolsIsEmpty() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNotNull(context.activeStreamingTools()); + assertTrue(context.activeStreamingTools().isEmpty()); + } + @Test + @Tag("valid") + void create_memoryServiceIsNullByDefault() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNull(context.memoryService()); + } + @Test + @Tag("valid") + void create_pluginManagerIsNullByDefault() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNull(context.pluginManager()); + } + @Test + @Tag("valid") + void create_withLiveRequestQueue_liveRequestQueueWrappedInOptional() { + LiveRequestQueue queue = new LiveRequestQueue(); + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + queue, + mockRunConfig + ); + assertTrue(context.liveRequestQueue().isPresent()); + assertSame(queue, context.liveRequestQueue().get()); + } + @Test + @Tag("valid") + void create_calledMultipleTimes_returnsDistinctInstances() { + InvocationContext context1 = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + InvocationContext context2 = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + assertNotSame(context1, context2); + } + @Test + @Tag("valid") + void create_withRealRunConfig_setsRunConfigCorrectly() { + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + runConfig + ); + assertNotNull(context); + assertSame(runConfig, context.runConfig()); + } + @Test + @Tag("boundary") + void create_withAllNullParameters_doesNotThrowException() { + assertDoesNotThrow(() -> InvocationContext.create( + null, + null, + null, + null, + null, + null + )); + } + @Test + @Tag("boundary") + void create_withAllNullExceptSession_returnsNonNullContext() { + InvocationContext context = InvocationContext.create( + null, + null, + null, + mockSession, + null, + null + ); + assertNotNull(context); + assertSame(mockSession, context.session()); + assertFalse(context.liveRequestQueue().isPresent()); + } + @Test + @Tag("integration") + void create_returnedContext_canBeUsedWithCopyOf() { + InvocationContext original = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + InvocationContext copy = InvocationContext.copyOf(original); + assertNotNull(copy); + assertSame(original.sessionService(), copy.sessionService()); + assertSame(original.artifactService(), copy.artifactService()); + assertSame(original.agent(), copy.agent()); + assertSame(original.session(), copy.session()); + assertEquals(original.liveRequestQueue(), copy.liveRequestQueue()); + assertSame(original.runConfig(), copy.runConfig()); + } + @Test + @Tag("integration") + void create_returnedContext_canSetAndGetBranch() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + context.branch("agentA.agentB"); + assertTrue(context.branch().isPresent()); + assertEquals("agentA.agentB", context.branch().get()); + } + @Test + @Tag("integration") + void create_returnedContext_canSetEndInvocation() { + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + context.setEndInvocation(true); + assertTrue(context.endInvocation()); + } + @Test + @Tag("integration") + void create_returnedContext_incrementLlmCallsCountThrowsWhenLimitExceeded() { + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(1).build(); + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + runConfig + ); + assertDoesNotThrow(() -> context.incrementLlmCallsCount()); + assertThrows(LlmCallsLimitExceededException.class, () -> context.incrementLlmCallsCount()); + } + @Test + @Tag("integration") + void create_returnedContext_equalsWithCopyOf() { + InvocationContext original = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + mockLiveRequestQueue, + mockRunConfig + ); + InvocationContext copy = InvocationContext. \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextCreateTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextCreateTest.java new file mode 100755 index 000000000..f474069ba --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextCreateTest.java @@ -0,0 +1,540 @@ +/* + * 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=create_a8299eaa31 +ROOST_METHOD_SIG_HASH=create_95f62994c3 + +Scenario 1: Successfully Create InvocationContext With All Valid Parameters + +Details: + TestName: createWithAllValidParametersReturnsCorrectInvocationContext + Description: Verifies that the `create` method successfully builds and returns a valid + `InvocationContext` instance when all parameters are provided with valid, + non-null values. This is the primary happy-path scenario. + +Execution: + Arrange: + - Create a mock instance of `BaseSessionService`. + - Create a mock instance of `BaseArtifactService`. + - Create a specific invocation ID string (e.g., "test-invocation-id-001"). + - Create a mock instance of `BaseAgent`. + - Create a mock instance of `Session`. + - Create a non-null `Content` object representing user content. + - Create a `RunConfig` instance using `RunConfig.builder().build()`. + + Act: + - Call `InvocationContext.create(sessionService, artifactService, invocationId, + agent, session, userContent, runConfig)`. + + Assert: + - Assert that the returned `InvocationContext` object is not null. + - Assert that `context.invocationId()` equals the provided invocation ID string. + - Assert that `context.sessionService()` is the same reference as the mock sessionService. + - Assert that `context.artifactService()` is the same reference as the mock artifactService. + - Assert that `context.agent()` is the same reference as the mock agent. + - Assert that `context.session()` is the same reference as the mock session. + - Assert that `context.userContent()` is present (i.e., `Optional.isPresent()` is true). + - Assert that `context.userContent().get()` is the same reference as the provided `Content` object. + - Assert that `context.runConfig()` is the same reference as the provided `runConfig`. + +Validation: + This test validates the core contract of the `create` factory method — that all provided + parameters are correctly passed through to the underlying builder and reflected in the + constructed `InvocationContext` object. It confirms the method is functioning as a + correct delegation to the builder pattern. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.common.collect.ImmutableSet; +import com.google.genai.types.Content; +import com.google.genai.types.Part; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.genai.types.FunctionCall; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextCreateTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseAgent mockAgent; + @Mock + private Session mockSession; + private RunConfig runConfig; + private String invocationId; + private Content userContent; + @BeforeEach + void setUp() { + invocationId = "test-invocation-id-001"; + runConfig = RunConfig.builder().build(); + userContent = Content.fromParts(Part.fromText("Hello, world!")); + } + @Test + @Tag("valid") + void createWithAllValidParametersReturnsCorrectInvocationContext() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context, "Created InvocationContext should not be null"); + assertEquals(invocationId, context.invocationId(), "Invocation ID should match"); + assertSame(mockSessionService, context.sessionService(), "Session service should be the same reference"); + assertSame(mockArtifactService, context.artifactService(), "Artifact service should be the same reference"); + assertSame(mockAgent, context.agent(), "Agent should be the same reference"); + assertSame(mockSession, context.session(), "Session should be the same reference"); + assertTrue(context.userContent().isPresent(), "User content should be present"); + assertSame(userContent, context.userContent().get(), "User content should be the same reference"); + assertSame(runConfig, context.runConfig(), "RunConfig should be the same reference"); + } + @Test + @Tag("valid") + void createWithNullUserContentReturnsContextWithEmptyUserContent() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + null, + runConfig + ); + // Assert + assertNotNull(context, "Created InvocationContext should not be null"); + assertFalse(context.userContent().isPresent(), "User content should be empty when null is passed"); + assertEquals(Optional.empty(), context.userContent(), "User content should be Optional.empty()"); + } + @Test + @Tag("valid") + void createWithValidInvocationIdStoresInvocationIdCorrectly() { + // Arrange + String specificInvocationId = "e-" + UUID.randomUUID(); + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + specificInvocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context); + assertEquals(specificInvocationId, context.invocationId(), "Invocation ID should match exactly"); + } + @Test + @Tag("valid") + void createWithNullArtifactServiceReturnsContextWithNullArtifactService() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + null, + invocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context, "Context should still be created even with null artifactService"); + assertNull(context.artifactService(), "Artifact service should be null"); + } + @Test + @Tag("valid") + void createWithNullSessionServiceReturnsContextWithNullSessionService() { + // Act + InvocationContext context = InvocationContext.create( + null, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context, "Context should be created even with null sessionService"); + assertNull(context.sessionService(), "Session service should be null"); + } + @Test + @Tag("valid") + void createWithNullRunConfigReturnsContextWithNullRunConfig() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + null + ); + // Assert + assertNotNull(context, "Context should be created even with null runConfig"); + assertNull(context.runConfig(), "RunConfig should be null"); + } + @Test + @Tag("valid") + void createSetsDefaultEndInvocationToFalse() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertFalse(context.endInvocation(), "endInvocation should default to false"); + } + @Test + @Tag("valid") + void createReturnsBranchAsEmptyByDefault() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context.branch(), "branch Optional should not be null"); + assertFalse(context.branch().isPresent(), "branch should be empty by default"); + } + @Test + @Tag("valid") + void createReturnsNullMemoryServiceByDefault() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNull(context.memoryService(), "Memory service should be null by default"); + } + @Test + @Tag("valid") + void createReturnsNullPluginManagerByDefault() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNull(context.pluginManager(), "Plugin manager should be null by default"); + } + @Test + @Tag("valid") + void createReturnsEmptyActiveStreamingToolsByDefault() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context.activeStreamingTools(), "Active streaming tools map should not be null"); + assertTrue(context.activeStreamingTools().isEmpty(), "Active streaming tools map should be empty by default"); + } + @Test + @Tag("valid") + void createReturnsLiveRequestQueueAsEmptyByDefault() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context.liveRequestQueue(), "liveRequestQueue Optional should not be null"); + assertFalse(context.liveRequestQueue().isPresent(), "liveRequestQueue should be empty by default"); + } + @Test + @Tag("boundary") + void createWithEmptyStringInvocationIdStoresEmptyString() { + // Arrange + String emptyInvocationId = ""; + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + emptyInvocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context); + assertEquals("", context.invocationId(), "Invocation ID should be empty string"); + } + @Test + @Tag("boundary") + void createWithNullInvocationIdStoresNullInvocationId() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + null, + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context); + assertNull(context.invocationId(), "Invocation ID should be null"); + } + @Test + @Tag("boundary") + void createWithNullAgentStoresNullAgent() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + null, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context); + assertNull(context.agent(), "Agent should be null"); + } + @Test + @Tag("boundary") + void createWithNullSessionStoresNullSession() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + null, + userContent, + runConfig + ); + // Assert + assertNotNull(context); + assertNull(context.session(), "Session should be null"); + } + @Test + @Tag("boundary") + void createWithAllNullParametersDoesNotThrowAndReturnsContext() { + // Act & Assert - should not throw + InvocationContext context = assertDoesNotThrow(() -> + InvocationContext.create(null, null, null, null, null, null, null) + ); + assertNotNull(context, "Context should not be null even with all null parameters"); + assertNull(context.sessionService()); + assertNull(context.artifactService()); + assertNull(context.invocationId()); + assertNull(context.agent()); + assertNull(context.session()); + assertFalse(context.userContent().isPresent()); + assertNull(context.runConfig()); + } + @Test + @Tag("valid") + void createWithRunConfigHavingMaxLlmCallsSetsRunConfigCorrectly() { + // Arrange + RunConfig configWithLlmLimit = RunConfig.builder().setMaxLlmCalls(100).build(); + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + configWithLlmLimit + ); + // Assert + assertNotNull(context); + assertNotNull(context.runConfig()); + assertEquals(100, context.runConfig().maxLlmCalls(), "RunConfig max LLM calls should be 100"); + } + @Test + @Tag("valid") + void createMultipleTimesReturnsDifferentInstances() { + // Act + InvocationContext context1 = InvocationContext.create( + mockSessionService, + mockArtifactService, + "invocation-id-1", + mockAgent, + mockSession, + userContent, + runConfig + ); + InvocationContext context2 = InvocationContext.create( + mockSessionService, + mockArtifactService, + "invocation-id-2", + mockAgent, + mockSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context1); + assertNotNull(context2); + assertNotSame(context1, context2, "Each call to create should return a new instance"); + assertNotEquals(context1.invocationId(), context2.invocationId(), "Invocation IDs should differ"); + } + @Test + @Tag("valid") + void createWrapsUserContentInOptional() { + // Arrange + Content nonNullContent = Content.fromParts(Part.fromText("test message")); + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + nonNullContent, + runConfig + ); + // Assert + assertNotNull(context); + assertEquals(Optional.of(nonNullContent), context.userContent(), + "userContent should be wrapped in Optional.of()"); + } + @Test + @Tag("valid") + void createWrapsNullUserContentInOptionalEmpty() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + null, + runConfig + ); + // Assert + assertNotNull(context); + assertEquals(Optional.empty(), context.userContent(), + "null userContent should be wrapped in Optional.empty()"); + } + @Test + @Tag("integration") + void createAndVerifyContextIsUsableWithSessionMethods() { + // Arrange + Session realSession = Session.builder("session-id-001") + .appName("testApp") + .userId("testUser") + .build(); + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + realSession, + userContent, + runConfig + ); + // Assert + assertNotNull(context); + assertEquals("session-id-001", context.session().id()); + assertEquals("testApp", context.appName()); + assertEquals("testUser", context.userId()); + } + @Test + @Tag("integration") + void createThenSetEndInvocationChangesState() { + // Act + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + invocationId, + mockAgent, + mockSession, + userContent, + runConfig + ); + assertFalse(context.endInvocation(), "endInvocation should be false initially"); + context.setEndInvocation(true); + // Assert + assertTrue(context.endInvocation(), "endInv \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextEndInvocationTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextEndInvocationTest.java new file mode 100644 index 000000000..19cdb8666 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextEndInvocationTest.java @@ -0,0 +1,457 @@ +/* + * 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=endInvocation_306878eff5 +ROOST_METHOD_SIG_HASH=endInvocation_b038f3ac95 + +Scenario 1: Default Value of endInvocation Returns False + +Details: + TestName: endInvocationReturnsFalseByDefault + Description: Verifies that when an InvocationContext is built using the builder without explicitly + setting the endInvocation flag, the endInvocation() method returns false, which is + the documented default value in the Builder class. +Execution: + Arrange: Create an InvocationContext using InvocationContext.builder() without calling + .endInvocation(true), providing only the minimum required fields (sessionService, + artifactService, invocationId, agent, session). + Act: Call endInvocation() on the constructed InvocationContext instance. + Assert: Use assertFalse() to confirm that endInvocation() returns false. +Validation: + The assertion confirms that the default value for the endInvocation field is false, as defined + in the Builder. This is significant because when an invocation is created, it should not be + in a terminated state by default; the system should only mark it as ended when explicitly + requested. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextEndInvocationTest { + @Mock private BaseSessionService mockSessionService; + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private PluginManager mockPluginManager; + @Mock private BaseAgent mockAgent; + @Mock private Session mockSession; + @Mock private ResumabilityConfig mockResumabilityConfig; + private static final String TEST_INVOCATION_ID = "test-invocation-id-001"; + private static final String TEST_APP_NAME = "testApp"; + private static final String TEST_USER_ID = "testUser"; + private static final String TEST_SESSION_ID = "testSession"; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn(TEST_APP_NAME); + when(mockSession.userId()).thenReturn(TEST_USER_ID); + when(mockSession.id()).thenReturn(TEST_SESSION_ID); + } + + @Test + @Tag("valid") + void endInvocationReturnsFalseByDefault() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .build(); + // Act + boolean result = context.endInvocation(); + // Assert + assertFalse(result, "endInvocation() should return false by default when not explicitly set"); + } + + @Test + @Tag("valid") + void endInvocationReturnsTrueWhenSetToTrue() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .endInvocation(true) + .build(); + // Act + boolean result = context.endInvocation(); + // Assert + assertTrue( + result, "endInvocation() should return true when explicitly set to true via builder"); + } + + @Test + @Tag("valid") + void endInvocationReturnsFalseWhenExplicitlySetToFalse() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .endInvocation(false) + .build(); + // Act + boolean result = context.endInvocation(); + // Assert + assertFalse( + result, "endInvocation() should return false when explicitly set to false via builder"); + } + + @Test + @Tag("valid") + void endInvocationReturnsTrueAfterSetEndInvocationCalledWithTrue() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .build(); + // Act + context.setEndInvocation(true); + boolean result = context.endInvocation(); + // Assert + assertTrue(result, "endInvocation() should return true after setEndInvocation(true) is called"); + } + + @Test + @Tag("valid") + void endInvocationReturnsFalseAfterSetEndInvocationCalledWithFalse() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .endInvocation(true) + .build(); + // Act + context.setEndInvocation(false); + boolean result = context.endInvocation(); + // Assert + assertFalse( + result, "endInvocation() should return false after setEndInvocation(false) is called"); + } + + @Test + @Tag("valid") + void endInvocationDefaultFalseWithAllFieldsSet() { + // Arrange + Content mockUserContent = mock(Content.class); + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockUserContent)) + .build(); + // Act + boolean result = context.endInvocation(); + // Assert + assertFalse( + result, + "endInvocation() should return false by default even when all other fields are set"); + } + + @Test + @Tag("valid") + void endInvocationTruePreservedAfterCopyOf() { + // Arrange + InvocationContext original = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .endInvocation(true) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + boolean result = copy.endInvocation(); + // Assert + assertTrue( + result, + "endInvocation() should return true in a copy when the original had endInvocation set to true"); + } + + @Test + @Tag("valid") + void endInvocationFalsePreservedAfterCopyOf() { + // Arrange + InvocationContext original = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .endInvocation(false) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + boolean result = copy.endInvocation(); + // Assert + assertFalse( + result, + "endInvocation() should return false in a copy when the original had endInvocation set to false"); + } + + @Test + @Tag("valid") + void endInvocationMutationDoesNotAffectCopiedContext() { + // Arrange + InvocationContext original = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .endInvocation(false) + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + // Act + original.setEndInvocation(true); + boolean originalResult = original.endInvocation(); + boolean copyResult = copy.endInvocation(); + // Assert + assertTrue(originalResult, "Original context should have endInvocation as true after mutation"); + assertFalse(copyResult, "Copied context should remain false after the original is mutated"); + } + + @Test + @Tag("boundary") + void endInvocationToggleFromFalseToTrue() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .build(); + // Assert initial state + assertFalse(context.endInvocation(), "Initial endInvocation() should be false"); + // Act + context.setEndInvocation(true); + // Assert toggled state + assertTrue(context.endInvocation(), "After toggling, endInvocation() should return true"); + } + + @Test + @Tag("boundary") + void endInvocationToggleFromTrueToFalse() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .endInvocation(true) + .build(); + // Assert initial state + assertTrue(context.endInvocation(), "Initial endInvocation() should be true"); + // Act + context.setEndInvocation(false); + // Assert toggled state + assertFalse( + context.endInvocation(), "After toggling back, endInvocation() should return false"); + } + + @Test + @Tag("boundary") + void endInvocationMultipleSetCalls() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .build(); + // Act and Assert multiple mutations + assertFalse(context.endInvocation(), "Initial state should be false"); + context.setEndInvocation(true); + assertTrue(context.endInvocation(), "After setting to true, should return true"); + context.setEndInvocation(true); + assertTrue(context.endInvocation(), "After setting to true again, should still return true"); + context.setEndInvocation(false); + assertFalse(context.endInvocation(), "After setting to false, should return false"); + context.setEndInvocation(false); + assertFalse(context.endInvocation(), "After setting to false again, should still return false"); + } + + @Test + @Tag("valid") + void endInvocationCreatedViaStaticFactoryMethodDefaultsFalse() { + // Arrange + Content mockUserContent = mock(Content.class); + InvocationContext context = + InvocationContext.create( + mockSessionService, + mockArtifactService, + TEST_INVOCATION_ID, + mockAgent, + mockSession, + mockUserContent, + null); + // Act + boolean result = context.endInvocation(); + // Assert + assertFalse( + result, "endInvocation() should return false when created via static factory method"); + } + + @Test + @Tag("integration") + void endInvocationConsistentWithEqualsAfterMutation() { + // Arrange + InvocationContext context1 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .endInvocation(false) + .build(); + InvocationContext context2 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .endInvocation(false) + .build(); + // Assert initial equality check on endInvocation + assertFalse(context1.endInvocation()); + assertFalse(context2.endInvocation()); + // Act - mutate context1 + context1.setEndInvocation(true); + // Assert after mutation + assertTrue(context1.endInvocation(), "context1.endInvocation() should be true after mutation"); + assertFalse( + context2.endInvocation(), + "context2.endInvocation() should remain false after mutating context1"); + } + + @Test + @Tag("valid") + void endInvocationWithBranchSetStillDefaultsFalse() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .branch("agentA.agentB") + .build(); + // Act + boolean result = context.endInvocation(); + // Assert + assertFalse(result, "endInvocation() should default to false regardless of branch being set"); + } + + @Test + @Tag("valid") + void endInvocationBranchMethodDoesNotAffectEndInvocationFlag() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .build(); + // Act + context.branch("someNewBranch"); + boolean result = context.endInvocation(); + // Assert + assertFalse(result, "Calling branch() setter should not affect endInvocation() value"); + } + + @Test + @Tag("valid") + void endInvocationAgentSetterDoesNotAffectEndInvocationFlag() { + // Arrange + BaseAgent anotherMockAgent = mock(BaseAgent.class); + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .build(); + // Act + context.agent(anotherMockAgent); + boolean result = context.endInvocation(); + // Assert + assertFalse(result, "Calling agent() setter should not affect endInvocation() value"); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextEqualsTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextEqualsTest.java new file mode 100644 index 000000000..8a0eb8f3f --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextEqualsTest.java @@ -0,0 +1,445 @@ +/* + * 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=equals_271e2b6b9b +ROOST_METHOD_SIG_HASH=equals_f2d574000d + +Scenario 1: Same Object Reference Returns True + +Details: + TestName: equalsReturnsTrueForSameObjectReference + Description: Verifies that the equals method returns true when an InvocationContext instance is compared to itself (same reference). This tests the identity check (this == o) at the beginning of the equals method. + +Execution: + Arrange: Create an InvocationContext instance using the builder with minimal required fields (sessionService, artifactService, invocationId, agent, session). + Act: Call equals on the instance passing the same instance as the argument. + Assert: Assert that the result is true. + +Validation: + This assertion verifies the reflexive property of the equals contract. When an object is compared to itself, it must return true. This is a fundamental requirement of the Java equals contract and ensures correctness in collections and comparison logic. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.genai.types.FunctionCall; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextEqualsTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private PluginManager mockPluginManager; + @Mock + private BaseAgent mockAgent; + @Mock + private Session mockSession; + @Mock + private RunConfig mockRunConfig; + @Mock + private ResumabilityConfig mockResumabilityConfig; + private String testInvocationId; + @BeforeEach + void setUp() { + testInvocationId = "e-" + UUID.randomUUID(); + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + } + private InvocationContext buildDefaultContext() { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + } + @Test + @Tag("valid") + void equalsReturnsTrueForSameObjectReference() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + boolean result = context.equals(context); + // Assert + assertTrue(result, "equals() should return true when comparing an object to itself"); + } + @Test + @Tag("valid") + void equalsReturnsTrueForTwoIdenticalContexts() { + // Arrange + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + boolean result = context1.equals(context2); + // Assert + assertTrue(result, "equals() should return true for two contexts with identical fields"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForNull() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + boolean result = context.equals(null); + // Assert + assertFalse(result, "equals() should return false when compared with null"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentType() { + // Arrange + InvocationContext context = buildDefaultContext(); + Object differentObject = "not an InvocationContext"; + // Act + boolean result = context.equals(differentObject); + // Assert + assertFalse(result, "equals() should return false when compared with a different type"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentInvocationId() { + // Arrange + String invocationId1 = "e-" + UUID.randomUUID(); + String invocationId2 = "e-" + UUID.randomUUID(); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(invocationId1) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(invocationId2) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + boolean result = context1.equals(context2); + // Assert + assertFalse(result, "equals() should return false when invocationIds differ"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentSessionService() { + // Arrange + BaseSessionService anotherSessionService = mock(BaseSessionService.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(anotherSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + boolean result = context1.equals(context2); + // Assert + assertFalse(result, "equals() should return false when sessionServices differ"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentArtifactService() { + // Arrange + BaseArtifactService anotherArtifactService = mock(BaseArtifactService.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(anotherArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + boolean result = context1.equals(context2); + // Assert + assertFalse(result, "equals() should return false when artifactServices differ"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentMemoryService() { + // Arrange + BaseMemoryService anotherMemoryService = mock(BaseMemoryService.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(anotherMemoryService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + boolean result = context1.equals(context2); + // Assert + assertFalse(result, "equals() should return false when memoryServices differ"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentPluginManager() { + // Arrange + PluginManager anotherPluginManager = mock(PluginManager.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(mockPluginManager) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(anotherPluginManager) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + boolean result = context1.equals(context2); + // Assert + assertFalse(result, "equals() should return false when pluginManagers differ"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentBranch() { + // Arrange + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch("branchA") + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch("branchB") + .build(); + // Act + boolean result = context1.equals(context2); + // Assert + assertFalse(result, "equals() should return false when branches differ"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentAgent() { + // Arrange + BaseAgent anotherAgent = mock(BaseAgent.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(anotherAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + boolean result = context1.equals(context2); + // Assert + assertFalse(result, "equals() should return false when agents differ"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentSession() { + // Arrange + Session anotherSession = mock(Session.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(anotherSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + boolean result = context1.equals(context2); + // Assert + assertFalse(result, "equals() should return false when sessions differ"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentRunConfig() { + // Arrange + RunConfig anotherRunConfig = mock(RunConfig.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(anotherRunConfig) + .build(); + // Act + boolean result = context1.equals(context2); + // Assert + assertFalse(result, "equals() should return false when runConfigs differ"); + } + @Test + @Tag("invalid") + void equalsReturnsFalseForDifferentEndInvocation() { + // Arrange + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(testInvocationId) \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextIncrementLlmCallsCountTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextIncrementLlmCallsCountTest.java new file mode 100644 index 000000000..bd4a38874 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextIncrementLlmCallsCountTest.java @@ -0,0 +1,388 @@ +/* + * 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=incrementLlmCallsCount_728a2657e3 +ROOST_METHOD_SIG_HASH=incrementLlmCallsCount_ccbe51a785 + +Scenario 1: Successful Single LLM Call Increment Without Limit Configured + +Details: + TestName: incrementLlmCallsCountSucceedsWhenNoLimitConfigured + Description: Verifies that calling incrementLlmCallsCount() once succeeds without throwing an exception + when the RunConfig has no maxLlmCalls limit set (default RunConfig with maxLlmCalls <= 0). + +Execution: + Arrange: + - Create a mock or stub for BaseSessionService, BaseArtifactService, and Session. + - Build a RunConfig using RunConfig.builder().build() (default, which means maxLlmCalls is 0 or not set). + - Build an InvocationContext using InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .session(mockSession) + .runConfig(defaultRunConfig) + .build(). + Act: + - Call invocationContext.incrementLlmCallsCount(). + Assert: + - No exception is thrown (the method completes normally). + +Validation: + Verifies that when maxLlmCalls is not configured (default of 0 or negative), no limit enforcement + occurs and the method completes without throwing LlmCallsLimitExceededException. This is important + to ensure the system operates without restrictions when no limit is intended. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextIncrementLlmCallsCountTest { + @Mock private BaseSessionService mockSessionService; + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private PluginManager mockPluginManager; + @Mock private Session mockSession; + @Mock private BaseAgent mockAgent; + private static final String TEST_INVOCATION_ID = "test-invocation-id"; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + when(mockSession.id()).thenReturn("testSessionId"); + } + + private InvocationContext buildContext(RunConfig runConfig) { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountSucceedsWhenNoLimitConfigured() { + // Arrange + RunConfig defaultRunConfig = RunConfig.builder().setMaxLlmCalls(0).build(); + InvocationContext invocationContext = buildContext(defaultRunConfig); + // Act & Assert + assertDoesNotThrow( + () -> invocationContext.incrementLlmCallsCount(), + "incrementLlmCallsCount should not throw when maxLlmCalls is 0 (no limit)"); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountSucceedsWithNullRunConfig() { + // Arrange + InvocationContext invocationContext = buildContext(null); + // Act & Assert + assertDoesNotThrow( + () -> invocationContext.incrementLlmCallsCount(), + "incrementLlmCallsCount should not throw when runConfig is null"); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountSucceedsWithNegativeMaxLlmCalls() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(-1).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act & Assert + assertDoesNotThrow( + () -> invocationContext.incrementLlmCallsCount(), + "incrementLlmCallsCount should not throw when maxLlmCalls is negative (no limit)"); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountSucceedsWhenCallsWithinLimit() { + // Arrange + int maxCalls = 5; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act & Assert - call 4 times, all within limit + assertDoesNotThrow( + () -> { + for (int i = 0; i < maxCalls - 1; i++) { + invocationContext.incrementLlmCallsCount(); + } + }, + "incrementLlmCallsCount should not throw when calls are within the limit"); + } + + @Test + @Tag("boundary") + void incrementLlmCallsCountSucceedsAtExactlyMaxLlmCalls() { + // Arrange + int maxCalls = 3; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act & Assert - call exactly maxCalls times, should not throw + assertDoesNotThrow( + () -> { + for (int i = 0; i < maxCalls; i++) { + invocationContext.incrementLlmCallsCount(); + } + }, + "incrementLlmCallsCount should not throw when called exactly maxLlmCalls times"); + } + + @Test + @Tag("boundary") + void incrementLlmCallsCountThrowsWhenExceedsLimitByOne() { + // Arrange + int maxCalls = 3; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act - call maxCalls times without throwing + assertDoesNotThrow( + () -> { + for (int i = 0; i < maxCalls; i++) { + invocationContext.incrementLlmCallsCount(); + } + }); + // Assert - the (maxCalls + 1)th call should throw + assertThrows( + LlmCallsLimitExceededException.class, + () -> invocationContext.incrementLlmCallsCount(), + "incrementLlmCallsCount should throw LlmCallsLimitExceededException when limit is exceeded by one"); + } + + @Test + @Tag("invalid") + void incrementLlmCallsCountThrowsWhenLimitExceeded() { + // Arrange + int maxCalls = 2; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act - exhaust the limit + assertDoesNotThrow( + () -> { + for (int i = 0; i < maxCalls; i++) { + invocationContext.incrementLlmCallsCount(); + } + }); + // Assert + LlmCallsLimitExceededException exception = + assertThrows( + LlmCallsLimitExceededException.class, + () -> invocationContext.incrementLlmCallsCount(), + "Should throw LlmCallsLimitExceededException when limit is exceeded"); + assertNotNull(exception.getMessage(), "Exception message should not be null"); + } + + @Test + @Tag("invalid") + void incrementLlmCallsCountThrowsWithCorrectMessageWhenLimitExceeded() { + // Arrange + int maxCalls = 1; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act - exhaust the limit + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + // Assert + LlmCallsLimitExceededException exception = + assertThrows( + LlmCallsLimitExceededException.class, + () -> invocationContext.incrementLlmCallsCount(), + "Should throw LlmCallsLimitExceededException with meaningful message"); + assertTrue( + exception.getMessage().contains(String.valueOf(maxCalls)), + "Exception message should contain the max limit value"); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountSucceedsWithSingleCallAndHighLimit() { + // Arrange + int maxCalls = 500; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act & Assert + assertDoesNotThrow( + () -> invocationContext.incrementLlmCallsCount(), + "A single call should succeed when maxLlmCalls is 500"); + } + + @Test + @Tag("boundary") + void incrementLlmCallsCountSucceedsWithMaxLlmCallsOfOne() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(1).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act & Assert - exactly 1 call should succeed + assertDoesNotThrow( + () -> invocationContext.incrementLlmCallsCount(), + "First call should succeed when maxLlmCalls is 1"); + } + + @Test + @Tag("boundary") + void incrementLlmCallsCountThrowsOnSecondCallWhenMaxLlmCallsIsOne() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(1).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act - first call should succeed + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + // Assert - second call should fail + assertThrows( + LlmCallsLimitExceededException.class, + () -> invocationContext.incrementLlmCallsCount(), + "Second call should throw when maxLlmCalls is 1"); + } + + @Test + @Tag("invalid") + void incrementLlmCallsCountThrowsMultipleTimesAfterLimitExceeded() { + // Arrange + int maxCalls = 2; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act - exhaust limit + assertDoesNotThrow( + () -> { + for (int i = 0; i < maxCalls; i++) { + invocationContext.incrementLlmCallsCount(); + } + }); + // Assert - multiple subsequent calls should also throw + assertThrows( + LlmCallsLimitExceededException.class, () -> invocationContext.incrementLlmCallsCount()); + assertThrows( + LlmCallsLimitExceededException.class, () -> invocationContext.incrementLlmCallsCount()); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountEachContextHasIndependentCounter() + throws LlmCallsLimitExceededException { + // Arrange + int maxCalls = 1; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext context1 = buildContext(runConfig); + InvocationContext context2 = buildContext(runConfig); + // Act - exhaust limit in context1 + context1.incrementLlmCallsCount(); + // Assert - context2 should still work independently + assertDoesNotThrow( + () -> context2.incrementLlmCallsCount(), + "context2 should have its own independent counter"); + // And context1 should throw + assertThrows( + LlmCallsLimitExceededException.class, + () -> context1.incrementLlmCallsCount(), + "context1 should throw as its limit is exceeded"); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountWithDefaultRunConfigDoesNotThrow() { + // Arrange + RunConfig defaultRunConfig = RunConfig.builder().build(); + InvocationContext invocationContext = buildContext(defaultRunConfig); + // Act & Assert - default maxLlmCalls is 500, calling once should not throw + assertDoesNotThrow( + () -> invocationContext.incrementLlmCallsCount(), + "Calling incrementLlmCallsCount once with default RunConfig should not throw"); + } + + @Test + @Tag("integration") + void incrementLlmCallsCountWithCopiedContextSharesNoCounterState() + throws LlmCallsLimitExceededException { + // Arrange + int maxCalls = 2; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext originalContext = buildContext(runConfig); + // Act - increment original context once + originalContext.incrementLlmCallsCount(); + // Copy the context (copy should start fresh internally based on InvocationCostManager being new + // per instance) + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + // Assert - copied context should be able to make its own calls + assertNotNull(copiedContext, "Copied context should not be null"); + assertDoesNotThrow( + () -> copiedContext.incrementLlmCallsCount(), + "Copied context should be able to increment LLM calls"); + } + + @Test + @Tag("boundary") + void incrementLlmCallsCountWithLargeMaxLlmCallsValue() { + // Arrange + int maxCalls = Integer.MAX_VALUE; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext invocationContext = buildContext(runConfig); + // Act & Assert - single call should be fine + assertDoesNotThrow( + () -> invocationContext.incrementLlmCallsCount(), + "Should not throw with very large maxLlmCalls and a single call"); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountDoesNotAffectOtherContextFields() + throws LlmCallsLimitExceededException { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(10).build(); + InvocationContext invocationContext = buildContext(runConfig); + String expectedInvocationId = TEST_INVOCATION_ID; + // Act + invocationContext.incrementLlmCallsCount(); + // Assert - other fields should remain unchanged + assertEquals( + expectedInvocationId, + invocationContext.invocationId(), + "invocationId should remain unchanged after incrementLlmCallsCount"); + assertEquals( + mockSession, + invocationContext.session(), + "session should remain unchanged after incrementLlmCallsCount"); + assertEquals( + runConfig, + invocationContext.runConfig(), + "runConfig should remain unchanged after incrementLlmCallsCount"); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextInvocationIdTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextInvocationIdTest.java new file mode 100755 index 000000000..cf0b15ddc --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextInvocationIdTest.java @@ -0,0 +1,405 @@ +/* + * 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=invocationId_b0959abc16 +ROOST_METHOD_SIG_HASH=invocationId_6cd6fb05e4 + +Scenario 1: Return the Explicitly Set Invocation ID + +Details: + TestName: invocationIdReturnsExplicitlySetValue + Description: Verifies that the `invocationId()` method returns the exact string value that was explicitly provided + to the builder via the `invocationId(String)` method during construction of the `InvocationContext`. + +Execution: + Arrange: Use `InvocationContext.builder()` and call `.invocationId("test-invocation-123")` along with any + other minimally required builder fields, then call `.build()` to produce an `InvocationContext` instance. + Act: Call `invocationId()` on the constructed `InvocationContext` instance. + Assert: Assert that the returned value equals `"test-invocation-123"` using `assertEquals`. + +Validation: + The assertion confirms that the `invocationId` field is correctly stored during construction and accurately + returned by the accessor method. This is critical because the invocation ID is the primary identifier for + tracing, logging, and correlating all operations within a single agent invocation lifecycle. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextInvocationIdTest { + @Mock private BaseSessionService mockSessionService; + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private PluginManager mockPluginManager; + @Mock private BaseAgent mockAgent; + @Mock private Session mockSession; + @Mock private RunConfig mockRunConfig; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + } + + private InvocationContext buildMinimalContext(String invocationId) { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(invocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + } + + @Test + @Tag("valid") + void invocationIdReturnsExplicitlySetValue() { + // Arrange + String expectedId = "test-invocation-123"; + InvocationContext context = buildMinimalContext(expectedId); + // Act + String actualId = context.invocationId(); + // Assert + assertEquals(expectedId, actualId); + } + + @Test + @Tag("valid") + void invocationIdReturnsUuidFormattedValue() { + // Arrange + String expectedId = UUID.randomUUID().toString(); + InvocationContext context = buildMinimalContext(expectedId); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + } + + @Test + @Tag("valid") + void invocationIdReturnsValueWithEPrefix() { + // Arrange + String expectedId = "e-" + UUID.randomUUID(); + InvocationContext context = buildMinimalContext(expectedId); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + assertTrue(actualId.startsWith("e-")); + } + + @Test + @Tag("valid") + void invocationIdReturnsSameValueOnMultipleCalls() { + // Arrange + String expectedId = "stable-invocation-id-456"; + InvocationContext context = buildMinimalContext(expectedId); + // Act + String firstCall = context.invocationId(); + String secondCall = context.invocationId(); + String thirdCall = context.invocationId(); + // Assert + assertEquals(expectedId, firstCall); + assertEquals(expectedId, secondCall); + assertEquals(expectedId, thirdCall); + assertEquals(firstCall, secondCall); + assertEquals(secondCall, thirdCall); + } + + @Test + @Tag("valid") + void invocationIdReturnsNullWhenNotSet() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + String actualId = context.invocationId(); + // Assert + assertNull(actualId); + } + + @Test + @Tag("boundary") + void invocationIdReturnsEmptyString() { + // Arrange + String expectedId = ""; + InvocationContext context = buildMinimalContext(expectedId); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + assertTrue(actualId.isEmpty()); + } + + @Test + @Tag("boundary") + void invocationIdReturnsVeryLongString() { + // Arrange + String expectedId = "e-" + "a".repeat(1000); + InvocationContext context = buildMinimalContext(expectedId); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + assertEquals(1002, actualId.length()); + } + + @Test + @Tag("boundary") + void invocationIdReturnsSingleCharacterString() { + // Arrange + String expectedId = "x"; + InvocationContext context = buildMinimalContext(expectedId); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + } + + @Test + @Tag("valid") + void invocationIdReturnsSpecialCharacters() { + // Arrange + String expectedId = "e-1234_abcd-WXYZ.test@domain"; + InvocationContext context = buildMinimalContext(expectedId); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + } + + @Test + @Tag("valid") + void invocationIdIsCorrectlySetViaCreateStaticMethod() { + // Arrange + String expectedId = "static-create-invocation-789"; + Content userContent = mock(Content.class); + InvocationContext context = + InvocationContext.create( + mockSessionService, + mockArtifactService, + expectedId, + mockAgent, + mockSession, + userContent, + mockRunConfig); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + } + + @Test + @Tag("valid") + void invocationIdReturnedByCopyOfMatchesOriginal() { + // Arrange + String expectedId = "original-invocation-id-999"; + InvocationContext original = buildMinimalContext(expectedId); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + String actualId = copy.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + assertEquals(original.invocationId(), copy.invocationId()); + } + + @Test + @Tag("integration") + void invocationIdRemainsUnchangedAfterBranchModification() { + // Arrange + String expectedId = "immutable-invocation-id-321"; + InvocationContext context = buildMinimalContext(expectedId); + // Act + context.branch("agentA.agentB"); + String actualId = context.invocationId(); + // Assert + assertEquals(expectedId, actualId); + } + + @Test + @Tag("integration") + void invocationIdRemainsUnchangedAfterEndInvocationSet() { + // Arrange + String expectedId = "end-invocation-id-654"; + InvocationContext context = buildMinimalContext(expectedId); + // Act + context.setEndInvocation(true); + String actualId = context.invocationId(); + // Assert + assertEquals(expectedId, actualId); + } + + @Test + @Tag("integration") + void invocationIdRemainsUnchangedAfterAgentUpdate() { + // Arrange + String expectedId = "agent-updated-invocation-id-321"; + InvocationContext context = buildMinimalContext(expectedId); + BaseAgent anotherAgent = mock(BaseAgent.class); + // Act + context.agent(anotherAgent); + String actualId = context.invocationId(); + // Assert + assertEquals(expectedId, actualId); + } + + @Test + @Tag("valid") + void invocationIdWithNumericOnlyString() { + // Arrange + String expectedId = "1234567890"; + InvocationContext context = buildMinimalContext(expectedId); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + } + + @Test + @Tag("valid") + void invocationIdMatchesNewInvocationContextIdFormat() { + // Arrange + String generatedId = InvocationContext.newInvocationContextId(); + InvocationContext context = buildMinimalContext(generatedId); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(generatedId, actualId); + assertNotNull(actualId); + assertTrue(actualId.startsWith("e-")); + } + + @Test + @Tag("valid") + void invocationIdNotEqualToADifferentId() { + // Arrange + String idOne = "invocation-id-one"; + String idTwo = "invocation-id-two"; + InvocationContext contextOne = buildMinimalContext(idOne); + InvocationContext contextTwo = buildMinimalContext(idTwo); + // Act + String actualIdOne = contextOne.invocationId(); + String actualIdTwo = contextTwo.invocationId(); + // Assert + assertNotEquals(actualIdOne, actualIdTwo); + assertEquals(idOne, actualIdOne); + assertEquals(idTwo, actualIdTwo); + } + + @Test + @Tag("valid") + void invocationIdReturnedCorrectlyWithFullBuilderSetup() { + // Arrange + String expectedId = "full-builder-invocation-id"; + Content userContent = mock(Content.class); + ResumabilityConfig resumabilityConfig = mock(ResumabilityConfig.class); + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId(expectedId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(userContent)) + .runConfig(mockRunConfig) + .branch(Optional.of("agentParent.agentChild")) + .endInvocation(false) + .resumabilityConfig(resumabilityConfig) + .build(); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + } + + @Test + @Tag("boundary") + void invocationIdWithWhitespaceOnlyString() { + // Arrange + String expectedId = " "; + InvocationContext context = buildMinimalContext(expectedId); + // Act + String actualId = context.invocationId(); + // Assert + assertNotNull(actualId); + assertEquals(expectedId, actualId); + assertEquals(3, actualId.length()); + } + + @Test + @Tag("valid") + void twoContextsWithSameInvocationIdAreConsistent() { + // Arrange + String sharedId = "shared-invocation-id"; + InvocationContext contextA = buildMinimalContext(sharedId); + InvocationContext contextB = buildMinimalContext(sharedId); + // Act + String idA = contextA.invocationId(); + String idB = contextB.invocationId(); + // Assert + assertEquals(idA, idB); + assertEquals(sharedId, idA); + assertEquals(sharedId, idB); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextIsResumableTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextIsResumableTest.java new file mode 100644 index 000000000..f7098442c --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextIsResumableTest.java @@ -0,0 +1,401 @@ +/* + * 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=isResumable_2397941e46 +ROOST_METHOD_SIG_HASH=isResumable_6fb85ad5c9 + +Scenario 1: Verify isResumable() Returns True When ResumabilityConfig Is Configured as Resumable + +Details: + TestName: isResumableReturnsTrueWhenResumabilityConfigIsResumable + Description: Verifies that the isResumable() method returns true when the ResumabilityConfig + instance injected into the InvocationContext is configured to indicate a resumable state. + This tests the direct delegation behavior from InvocationContext to ResumabilityConfig. + +Execution: + Arrange: + - Create a mock of ResumabilityConfig using Mockito. + - Stub the mock so that resumabilityConfig.isResumable() returns true. + - Build an InvocationContext using InvocationContext.builder() + with all required fields (sessionService, artifactService, session, agent) + and set the resumabilityConfig to the mocked instance via .resumabilityConfig(mockResumabilityConfig). + - Call .build() to instantiate the InvocationContext. + Act: + - Call invocationContext.isResumable(). + Assert: + - Use assertTrue(invocationContext.isResumable()) to confirm the result is true. + +Validation: + The assertion confirms that isResumable() faithfully delegates to and returns the value from + ResumabilityConfig.isResumable() when it returns true. This is critical to ensure the + InvocationContext correctly reflects the resumability state as configured, allowing downstream + logic (e.g., shouldPauseInvocation) to make correct decisions based on resumability. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.common.collect.ImmutableSet; +import com.google.genai.types.Content; +import com.google.genai.types.FunctionCall; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextIsResumableTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private PluginManager mockPluginManager; + @Mock + private BaseAgent mockAgent; + @Mock + private ResumabilityConfig mockResumabilityConfig; + private Session mockSession; + @BeforeEach + void setUp() { + mockSession = Session.builder("test-session-id") + .appName("test-app") + .userId("test-user") + .build(); + } + @Test + @Tag("valid") + void isResumableReturnsTrueWhenResumabilityConfigIsResumable() { + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertTrue(invocationContext.isResumable()); + } + @Test + @Tag("valid") + void isResumableReturnsFalseWhenResumabilityConfigIsNotResumable() { + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertFalse(invocationContext.isResumable()); + } + @Test + @Tag("valid") + void isResumableDelegatesToResumabilityConfigIsResumable() { + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .resumabilityConfig(mockResumabilityConfig) + .build(); + invocationContext.isResumable(); + verify(mockResumabilityConfig, times(1)).isResumable(); + } + @Test + @Tag("valid") + void isResumableReturnsTrueConsistentlyForMultipleCalls() { + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertTrue(invocationContext.isResumable()); + assertTrue(invocationContext.isResumable()); + assertTrue(invocationContext.isResumable()); + verify(mockResumabilityConfig, times(3)).isResumable(); + } + @Test + @Tag("valid") + void isResumableReturnsFalseConsistentlyForMultipleCalls() { + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertFalse(invocationContext.isResumable()); + assertFalse(invocationContext.isResumable()); + assertFalse(invocationContext.isResumable()); + verify(mockResumabilityConfig, times(3)).isResumable(); + } + @Test + @Tag("valid") + void isResumableWithAllOptionalFieldsSet() { + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId("full-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .branch(Optional.of("agent1.agent2")) + .endInvocation(false) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertTrue(invocationContext.isResumable()); + } + @Test + @Tag("valid") + void isResumableWithOnlyRequiredFieldsSet() { + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("minimal-invocation-id") + .agent(mockAgent) + .session(mockSession) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertTrue(invocationContext.isResumable()); + } + @Test + @Tag("integration") + void isResumableWithCopiedContextPreservesResumabilityConfig() { + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("original-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .resumabilityConfig(mockResumabilityConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertTrue(copiedContext.isResumable()); + verify(mockResumabilityConfig, times(2)).isResumable(); + } + @Test + @Tag("integration") + void isResumableWithCopiedContextPreservesNonResumableConfig() { + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("original-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .resumabilityConfig(mockResumabilityConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertFalse(copiedContext.isResumable()); + verify(mockResumabilityConfig, times(2)).isResumable(); + } + @Test + @Tag("valid") + void isResumableWithTrueDoesNotAffectEndInvocationState() { + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .endInvocation(false) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertTrue(invocationContext.isResumable()); + assertFalse(invocationContext.endInvocation()); + } + @Test + @Tag("valid") + void isResumableWithFalseDoesNotAffectEndInvocationState() { + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .endInvocation(false) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertFalse(invocationContext.isResumable()); + assertFalse(invocationContext.endInvocation()); + } + @Test + @Tag("valid") + void isResumableReturnsTrueWithBranchSet() { + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .branch("parentAgent.childAgent") + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertTrue(invocationContext.isResumable()); + assertTrue(invocationContext.branch().isPresent()); + } + @Test + @Tag("valid") + void isResumableReturnsFalseWithBranchSet() { + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .branch("parentAgent.childAgent") + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertFalse(invocationContext.isResumable()); + } + @Test + @Tag("integration") + void isResumableInteractionWithShouldPauseInvocationWhenNotResumable() { + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertFalse(invocationContext.isResumable()); + Event mockEvent = mock(Event.class); + assertFalse(invocationContext.shouldPauseInvocation(mockEvent)); + } + @Test + @Tag("boundary") + void isResumableWithNewInvocationContextIdUsedAsInvocationId() { + when(mockResumabilityConfig.isResumable()).thenReturn(true); + String newId = InvocationContext.newInvocationContextId(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(newId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertTrue(invocationContext.isResumable()); + assertTrue(newId.startsWith("e-")); + } + @Test + @Tag("valid") + void isResumableDoesNotMutateContextState() { + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .endInvocation(false) + .branch(Optional.of("test-branch")) + .resumabilityConfig(mockResumabilityConfig) + .build(); + boolean resultBefore = invocationContext.isResumable(); + boolean endInvocationBefore = invocationContext.endInvocation(); + Optional branchBefore = invocationContext.branch(); + invocationContext.isResumable(); + assertTrue(resultBefore); + assertFalse(endInvocationBefore); + assertTrue(branchBefore.isPresent()); + assertFalse(invocationContext.endInvocation()); + assertTrue(invocationContext.branch().isPresent()); + } + @Test + @Tag("valid") + void isResumableReflectsResumabilityConfigTrueReturnValue() { + ResumabilityConfig spyResumabilityConfig = mock(ResumabilityConfig.class); + when(spyResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .resumabilityConfig(spyResumabilityConfig) + .build(); + boolean result = invocationContext.isResumable(); + assertTrue(result); + verify(spyRes \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextLiveRequestQueueTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextLiveRequestQueueTest.java new file mode 100755 index 000000000..a4b0a2522 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextLiveRequestQueueTest.java @@ -0,0 +1,422 @@ +/* + * 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=liveRequestQueue_752dd14d55 +ROOST_METHOD_SIG_HASH=liveRequestQueue_66d4c7a2b5 + +Scenario 1: Return Present Optional When LiveRequestQueue Is Explicitly Set + +Details: + TestName: liveRequestQueueReturnsPresentOptionalWhenQueueIsSet + Description: Verifies that the liveRequestQueue() method returns a non-empty Optional when a LiveRequestQueue + instance is explicitly provided to the builder during InvocationContext construction. +Execution: + Arrange: Create a mock or real LiveRequestQueue instance. Use InvocationContext.builder() and call + .liveRequestQueue(liveRequestQueueInstance) to set the queue. Also set all other required fields + (sessionService, artifactService, agent, session, runConfig). Call .build() to produce the context. + Act: Invoke context.liveRequestQueue() on the built InvocationContext instance. + Assert: Use assertTrue(context.liveRequestQueue().isPresent()) to confirm the Optional is not empty. + Use assertEquals(liveRequestQueueInstance, context.liveRequestQueue().get()) to confirm the same + instance is returned. +Validation: + This assertion verifies that when a LiveRequestQueue is provided through the builder, the method correctly + wraps it in a non-empty Optional and returns it. This is critical for live/streaming agent interactions + where the queue is the communication channel between the client and the agent runtime. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.types.Content; +import com.google.genai.types.FunctionCall; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.errorprone.annotations.InlineMe; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextLiveRequestQueueTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private PluginManager mockPluginManager; + @Mock + private BaseAgent mockAgent; + private Session mockSession; + private RunConfig runConfig; + @BeforeEach + void setUp() { + mockSession = Session.builder("test-session-id") + .appName("test-app") + .userId("test-user") + .build(); + runConfig = RunConfig.builder().build(); + } + // ----------------------------------------------------------------------- + // Scenario 1: Return Present Optional When LiveRequestQueue Is Explicitly Set + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void liveRequestQueueReturnsPresentOptionalWhenQueueIsSet() { + // Arrange + LiveRequestQueue liveRequestQueueInstance = new LiveRequestQueue(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .liveRequestQueue(Optional.of(liveRequestQueueInstance)) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent(), "LiveRequestQueue Optional should be present when queue is set"); + assertEquals(liveRequestQueueInstance, result.get(), "The returned LiveRequestQueue should be the same instance that was set"); + } + // ----------------------------------------------------------------------- + // Scenario 2: Return Empty Optional When LiveRequestQueue Is Not Set + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void liveRequestQueueReturnsEmptyOptionalWhenQueueIsNotSet() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertNotNull(result, "liveRequestQueue() should never return null, even when not set"); + assertFalse(result.isPresent(), "LiveRequestQueue Optional should be empty when queue was not set"); + } + // ----------------------------------------------------------------------- + // Scenario 3: Return Empty Optional When LiveRequestQueue Is Explicitly Set to Empty Optional + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void liveRequestQueueReturnsEmptyOptionalWhenExplicitlySetToEmpty() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .liveRequestQueue(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertNotNull(result, "liveRequestQueue() should never return null"); + assertFalse(result.isPresent(), "LiveRequestQueue Optional should be empty when explicitly set to Optional.empty()"); + } + // ----------------------------------------------------------------------- + // Scenario 4: Return Present Optional When Using the Direct LiveRequestQueue Builder Method + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void liveRequestQueueReturnsPresentOptionalWhenSetViaDirectBuilderMethod() { + // Arrange + LiveRequestQueue liveRequestQueueInstance = new LiveRequestQueue(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .liveRequestQueue(liveRequestQueueInstance) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent(), "LiveRequestQueue Optional should be present when queue is set via direct builder method"); + assertSame(liveRequestQueueInstance, result.get(), "The returned LiveRequestQueue should be the same instance"); + } + // ----------------------------------------------------------------------- + // Scenario 5: Return Same LiveRequestQueue After CopyOf + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void liveRequestQueueIsCopiedCorrectlyViaCopyOf() { + // Arrange + LiveRequestQueue liveRequestQueueInstance = new LiveRequestQueue(); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .liveRequestQueue(Optional.of(liveRequestQueueInstance)) + .runConfig(runConfig) + .build(); + // Act + InvocationContext copied = InvocationContext.copyOf(original); + // Assert + assertTrue(copied.liveRequestQueue().isPresent(), "Copied context should have the LiveRequestQueue present"); + assertSame(liveRequestQueueInstance, copied.liveRequestQueue().get(), "Copied context should reference the same LiveRequestQueue instance"); + } + // ----------------------------------------------------------------------- + // Scenario 6: LiveRequestQueue Not Present After CopyOf When Original Has None + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void liveRequestQueueIsEmptyAfterCopyOfWhenOriginalHasNone() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + InvocationContext copied = InvocationContext.copyOf(original); + // Assert + assertNotNull(copied.liveRequestQueue(), "liveRequestQueue() should not return null after copyOf"); + assertFalse(copied.liveRequestQueue().isPresent(), "Copied context should have empty LiveRequestQueue when original had none"); + } + // ----------------------------------------------------------------------- + // Scenario 7: liveRequestQueue() Returns non-null Result (Integration) + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void liveRequestQueueReturnNonNullOptionalAlways() { + // Arrange + InvocationContext contextWithQueue = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .liveRequestQueue(new LiveRequestQueue()) + .runConfig(runConfig) + .build(); + InvocationContext contextWithoutQueue = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act & Assert + assertNotNull(contextWithQueue.liveRequestQueue(), "liveRequestQueue() should not return null when set"); + assertNotNull(contextWithoutQueue.liveRequestQueue(), "liveRequestQueue() should not return null when not set"); + } + // ----------------------------------------------------------------------- + // Scenario 8: LiveRequestQueue Presence Verified via Static create Method + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void liveRequestQueueReturnsPresentOptionalWhenContextCreatedViaStaticCreate() { + // Arrange + LiveRequestQueue liveRequestQueueInstance = new LiveRequestQueue(); + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + liveRequestQueueInstance, + runConfig + ); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent(), "LiveRequestQueue Optional should be present when created via static create method"); + assertSame(liveRequestQueueInstance, result.get(), "The returned LiveRequestQueue should be the same instance passed to create"); + } + // ----------------------------------------------------------------------- + // Scenario 9: LiveRequestQueue Is Empty When Created With Null via Static create Method + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void liveRequestQueueReturnsEmptyOptionalWhenNullPassedToStaticCreate() { + // Arrange + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + (LiveRequestQueue) null, + runConfig + ); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertNotNull(result, "liveRequestQueue() should not return null"); + assertFalse(result.isPresent(), "LiveRequestQueue Optional should be empty when null is passed to static create"); + } + // ----------------------------------------------------------------------- + // Scenario 10: Two Contexts With Same Queue Are Equal + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void twoContextsWithSameLiveRequestQueueAreEqual() { + // Arrange + LiveRequestQueue liveRequestQueueInstance = new LiveRequestQueue(); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .liveRequestQueue(Optional.of(liveRequestQueueInstance)) + .invocationId("test-invocation-id") + .runConfig(runConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .liveRequestQueue(Optional.of(liveRequestQueueInstance)) + .invocationId("test-invocation-id") + .runConfig(runConfig) + .build(); + // Act & Assert + assertEquals(context1.liveRequestQueue(), context2.liveRequestQueue(), + "Two contexts configured with the same LiveRequestQueue should return equal Optionals"); + } + // ----------------------------------------------------------------------- + // Scenario 11: LiveRequestQueue Present When Using Optional Wrapper Builder + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void liveRequestQueueReturnsPresentWhenSetViaOptionalWrapperBuilder() { + // Arrange + LiveRequestQueue liveRequestQueueInstance = new LiveRequestQueue(); + Optional optionalQueue = Optional.of(liveRequestQueueInstance); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .liveRequestQueue(optionalQueue) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent(), "Optional should be present"); + assertEquals(optionalQueue, result, "The returned Optional should equal the one set via builder"); + } + // ----------------------------------------------------------------------- + // Scenario 12: LiveRequestQueue Is Consistent Across Multiple Calls + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void liveRequestQueueReturnsSameInstanceOnMultipleCalls() { + // Arrange + LiveRequestQueue liveRequestQueueInstance = new LiveRequestQueue(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .liveRequestQueue(Optional.of(liveRequestQueueInstance)) + .runConfig(runConfig) + .build(); + // Act + Optional firstCall = context.liveRequestQueue(); + Optional secondCall = context.liveRequestQueue(); + // Assert + assertEquals(firstCall, secondCall, "Multiple calls to liveRequestQueue() should return consistent results"); + assertSame(firstCall.get(), secondCall.get(), "Multiple calls should return the same LiveRequestQueue instance"); + } + // ----------------------------------------------------------------------- + // Scenario 13: liveRequestQueue() With Deprecated Constructor + // ----------------------------------------------------------------------- + @SuppressWarnings("deprecation") + @Test + @Tag("boundary") + void liveRequestQueueReturnsPresentOptionalWhenSetViaDeprecatedConstructor() { + // Arrange + LiveRequestQueue liveRequestQueueInstance = new LiveRequestQueue(); + Optional optionalQueue = Optional.of(liveRequestQueueInstance); + InvocationContext context = new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + optionalQueue, + Optional.empty(), + "test-invocation-id", + mockAgent, + mockSession, + Optional.empty(), + runConfig, + false + ); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent(), "LiveRequestQueue Optional should be present when set via deprecated constructor"); + assertSame(liveRequestQueueInstance, result.get(), "The returned instance should be same as set via deprecated constructor"); + } + // ----------------------------------------------------------------------- + // Scenario 14: Empty LiveRequestQueue via Deprecated Constructor + // ----------------------------------------------------------------------- + @SuppressWarnings("deprecation") + @Test + @Tag("boundary") + void liveRequestQueueReturnsEmptyOptionalWhenEmptyOptionalPassedViaDeprecatedConstructor() { + // Arrange + InvocationContext context = new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + Optional.empty(), + Optional.empty(), + "test-invocation-id", + mockAgent, + mockSession, + Optional.empty(), + runConfig, + false + ); \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextMemoryServiceTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextMemoryServiceTest.java new file mode 100755 index 000000000..b502ec8bd --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextMemoryServiceTest.java @@ -0,0 +1,430 @@ +/* + * 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=memoryService_1115878a43 +ROOST_METHOD_SIG_HASH=memoryService_b8500b83f5 + +Scenario 1: Memory Service Returns the Correct Non-Null Instance Set via Builder + +Details: + TestName: memoryServiceReturnsCorrectInstanceWhenSetViaBuilder + Description: Verifies that the `memoryService()` method returns the exact same + `BaseMemoryService` instance that was provided to the builder during + construction of `InvocationContext`. This test ensures the field + assignment and retrieval pipeline works correctly end-to-end. + +Execution: + Arrange: + - Create a mock instance of `BaseMemoryService` using a mocking framework (e.g., Mockito). + - Create a mock instance of `BaseSessionService`. + - Create a mock instance of `BaseArtifactService`. + - Create a mock instance of `BaseAgent`. + - Create a mock instance of `Session`. + - Use `InvocationContext.builder()` and supply the mocked `BaseMemoryService` + via `.memoryService(mockMemoryService)`, along with other required fields + (sessionService, artifactService, agent, session). + - Call `.build()` to produce the `InvocationContext` instance. + + Act: + - Invoke `invocationContext.memoryService()` on the built instance. + + Assert: + - Use `assertSame(mockMemoryService, invocationContext.memoryService())` to + confirm the returned reference is identical to the mock supplied at build time. + +Validation: + The assertion verifies referential equality between the provided mock and the + returned value, ensuring no defensive copying or wrapping occurs. This is + significant because downstream components (e.g., LLM flows) depend on receiving + the exact service instance to perform memory lookups and storage operations + consistently throughout an invocation lifecycle. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextMemoryServiceTest { + @Mock private BaseSessionService mockSessionService; + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private BaseAgent mockAgent; + @Mock private Session mockSession; + @Mock private PluginManager mockPluginManager; + @Mock private RunConfig mockRunConfig; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + } + + @Test + @Tag("valid") + void memoryServiceReturnsCorrectInstanceWhenSetViaBuilder() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseMemoryService result = invocationContext.memoryService(); + assertSame(mockMemoryService, result); + } + + @Test + @Tag("valid") + void memoryServiceReturnsNullWhenNotSetViaBuilder() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseMemoryService result = invocationContext.memoryService(); + assertNull(result); + } + + @Test + @Tag("valid") + void memoryServiceReturnsSameInstanceOnMultipleCalls() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseMemoryService firstCall = invocationContext.memoryService(); + BaseMemoryService secondCall = invocationContext.memoryService(); + assertSame(firstCall, secondCall); + } + + @Test + @Tag("valid") + void memoryServiceReturnsDifferentInstancesForDifferentContexts() { + BaseMemoryService mockMemoryService2 = mock(BaseMemoryService.class); + InvocationContext context1 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("invocation-id-1") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService2) + .agent(mockAgent) + .session(mockSession) + .invocationId("invocation-id-2") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(mockMemoryService, context1.memoryService()); + assertSame(mockMemoryService2, context2.memoryService()); + assertNotSame(context1.memoryService(), context2.memoryService()); + } + + @Test + @Tag("valid") + void memoryServiceReturnedIsNotNull() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertNotNull(invocationContext.memoryService()); + } + + @Test + @Tag("valid") + void memoryServicePreservedThroughCopyOf() { + InvocationContext original = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + assertSame(mockMemoryService, copy.memoryService()); + } + + @Test + @Tag("valid") + void memoryServicePreservedAfterBranchUpdate() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + invocationContext.branch("testBranch"); + assertSame(mockMemoryService, invocationContext.memoryService()); + } + + @Test + @Tag("valid") + void memoryServicePreservedAfterAgentUpdate() { + BaseAgent newMockAgent = mock(BaseAgent.class); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + invocationContext.agent(newMockAgent); + assertSame(mockMemoryService, invocationContext.memoryService()); + } + + @Test + @Tag("valid") + void memoryServicePreservedAfterEndInvocationSet() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + invocationContext.setEndInvocation(true); + assertSame(mockMemoryService, invocationContext.memoryService()); + } + + @Test + @Tag("valid") + void memoryServiceWithStaticCreateMethod() { + Content mockContent = mock(Content.class); + InvocationContext invocationContext = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "test-invocation-id", + mockAgent, + mockSession, + mockContent, + mockRunConfig); + assertNull(invocationContext.memoryService()); + } + + @Test + @Tag("integration") + void memoryServiceWithAllFieldsSet() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .agent(mockAgent) + .session(mockSession) + .invocationId("full-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + assertSame(mockMemoryService, invocationContext.memoryService()); + assertNotNull(invocationContext.sessionService()); + assertNotNull(invocationContext.artifactService()); + assertNotNull(invocationContext.pluginManager()); + } + + @Test + @Tag("valid") + void memoryServiceEqualityBetweenTwoContextsWithSameService() { + InvocationContext context1 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(context1.memoryService(), context2.memoryService()); + } + + @Test + @Tag("boundary") + void memoryServiceNullExplicitlyNotSetInBuilder() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .runConfig(mockRunConfig) + .build(); + BaseMemoryService result = invocationContext.memoryService(); + assertNull(result); + } + + @Test + @Tag("valid") + void memoryServiceWithBranchBuilderMethod() { + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .branch("agentA.agentB") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(mockMemoryService, invocationContext.memoryService()); + } + + @Test + @Tag("integration") + void memoryServiceRetainedAfterCopyOfWithActiveStreamingTools() { + InvocationContext original = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(original); + assertSame(mockMemoryService, copiedContext.memoryService()); + assertEquals(original.memoryService(), copiedContext.memoryService()); + } + + @Test + @Tag("valid") + void memoryServiceWithLiveRequestQueueSet() { + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .liveRequestQueue(liveRequestQueue) + .runConfig(mockRunConfig) + .build(); + assertSame(mockMemoryService, invocationContext.memoryService()); + } + + @Test + @Tag("boundary") + void memoryServiceDoesNotAffectOtherFields() { + String expectedInvocationId = "specific-invocation-id"; + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId(expectedInvocationId) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(mockMemoryService, invocationContext.memoryService()); + assertEquals(expectedInvocationId, invocationContext.invocationId()); + assertSame(mockSessionService, invocationContext.sessionService()); + assertSame(mockArtifactService, invocationContext.artifactService()); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextNewInvocationContextIdTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextNewInvocationContextIdTest.java new file mode 100644 index 000000000..5fd6171ae --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextNewInvocationContextIdTest.java @@ -0,0 +1,330 @@ +/* + * 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=newInvocationContextId_87a927076b +ROOST_METHOD_SIG_HASH=newInvocationContextId_db44e2dbb1 + +Scenario 1: Verify that the returned ID starts with the expected prefix "e-" + +Details: + TestName: newInvocationContextIdStartsWithExpectedPrefix + Description: Verifies that the string returned by newInvocationContextId() always begins + with the literal prefix "e-", which is the defined convention for invocation + context identifiers in the system. +Execution: + Arrange: No setup is required since newInvocationContextId() is a static method with no + parameters or dependencies. + Act: Call InvocationContext.newInvocationContextId() and store the result in a String variable. + Assert: Use assertTrue or assertThat to confirm that the returned string starts with "e-". +Validation: + The assertion verifies that the prefix "e-" is consistently applied to all generated IDs. + This is significant because other parts of the system (e.g., InvocationContext.Builder which + uses newInvocationContextId() to set the default invocationId) may depend on this prefix + format for routing, logging, or identification purposes. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.regex.Pattern; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +public class InvocationContextNewInvocationContextIdTest { + private static final String EXPECTED_PREFIX = "e-"; + private static final Pattern UUID_PATTERN = + Pattern.compile("^e-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"); + + @Test + @Tag("valid") + void newInvocationContextIdStartsWithExpectedPrefix() { + String result = InvocationContext.newInvocationContextId(); + assertNotNull(result, "Generated ID should not be null"); + assertTrue( + result.startsWith(EXPECTED_PREFIX), + "Generated ID should start with 'e-' but was: " + result); + } + + @Test + @Tag("valid") + void newInvocationContextIdIsNotNull() { + String result = InvocationContext.newInvocationContextId(); + assertNotNull(result, "Generated ID must not be null"); + } + + @Test + @Tag("valid") + void newInvocationContextIdIsNotEmpty() { + String result = InvocationContext.newInvocationContextId(); + assertFalse(result.isEmpty(), "Generated ID must not be empty"); + } + + @Test + @Tag("valid") + void newInvocationContextIdMatchesExpectedPattern() { + String result = InvocationContext.newInvocationContextId(); + assertTrue( + UUID_PATTERN.matcher(result).matches(), + "Generated ID should match pattern 'e-' but was: " + result); + } + + @Test + @Tag("valid") + void newInvocationContextIdHasCorrectPrefixLength() { + String result = InvocationContext.newInvocationContextId(); + assertTrue( + result.length() > EXPECTED_PREFIX.length(), + "Generated ID should have content after the prefix 'e-'"); + } + + @Test + @Tag("valid") + void newInvocationContextIdContainsValidUuidAfterPrefix() { + String result = InvocationContext.newInvocationContextId(); + String uuidPart = result.substring(EXPECTED_PREFIX.length()); + assertDoesNotThrow( + () -> UUID.fromString(uuidPart), + "The part after 'e-' should be a valid UUID but was: " + uuidPart); + } + + @Test + @Tag("valid") + void newInvocationContextIdUuidPartIsVersion4() { + String result = InvocationContext.newInvocationContextId(); + String uuidPart = result.substring(EXPECTED_PREFIX.length()); + UUID uuid = UUID.fromString(uuidPart); + assertEquals(4, uuid.version(), "The UUID in the generated ID should be version 4 (random)"); + } + + @Test + @Tag("valid") + void newInvocationContextIdHasExpectedTotalLength() { + // "e-" (2 chars) + UUID standard representation (36 chars) = 38 chars + int expectedLength = 2 + 36; + String result = InvocationContext.newInvocationContextId(); + assertEquals( + expectedLength, + result.length(), + "Generated ID should have length " + expectedLength + " but was: " + result.length()); + } + + @Test + @Tag("valid") + void newInvocationContextIdPrefixExactlyTwoChars() { + String result = InvocationContext.newInvocationContextId(); + assertEquals("e-", result.substring(0, 2), "Prefix should be exactly 'e-'"); + } + + @RepeatedTest(10) + @Tag("valid") + void newInvocationContextIdAlwaysStartsWithPrefix() { + String result = InvocationContext.newInvocationContextId(); + assertTrue( + result.startsWith("e-"), + "Every generated ID should always start with 'e-' but got: " + result); + } + + @Test + @Tag("valid") + void newInvocationContextIdGeneratesTwoUniqueIds() { + String id1 = InvocationContext.newInvocationContextId(); + String id2 = InvocationContext.newInvocationContextId(); + assertNotEquals(id1, id2, "Two consecutively generated IDs should be unique"); + } + + @Test + @Tag("valid") + void newInvocationContextIdGeneratesMultipleUniqueIds() { + int count = 100; + Set generatedIds = new HashSet<>(); + for (int i = 0; i < count; i++) { + generatedIds.add(InvocationContext.newInvocationContextId()); + } + assertEquals(count, generatedIds.size(), "All " + count + " generated IDs should be unique"); + } + + @Test + @Tag("valid") + void newInvocationContextIdAllGeneratedIdsHaveCorrectPrefix() { + int count = 50; + for (int i = 0; i < count; i++) { + String id = InvocationContext.newInvocationContextId(); + assertTrue( + id.startsWith("e-"), "ID at iteration " + i + " should start with 'e-' but was: " + id); + } + } + + @Test + @Tag("valid") + void newInvocationContextIdAllGeneratedIdsMatchPattern() { + int count = 50; + for (int i = 0; i < count; i++) { + String id = InvocationContext.newInvocationContextId(); + assertTrue( + UUID_PATTERN.matcher(id).matches(), + "ID at iteration " + i + " should match UUID pattern but was: " + id); + } + } + + @Test + @Tag("boundary") + void newInvocationContextIdFirstCharIsE() { + String result = InvocationContext.newInvocationContextId(); + assertEquals('e', result.charAt(0), "First character of generated ID should be 'e'"); + } + + @Test + @Tag("boundary") + void newInvocationContextIdSecondCharIsDash() { + String result = InvocationContext.newInvocationContextId(); + assertEquals('-', result.charAt(1), "Second character of generated ID should be '-'"); + } + + @Test + @Tag("boundary") + void newInvocationContextIdMinimumLength() { + String result = InvocationContext.newInvocationContextId(); + assertTrue( + result.length() >= 38, + "Generated ID should have at least 38 characters (prefix + UUID) but had: " + + result.length()); + } + + @Test + @Tag("boundary") + void newInvocationContextIdMaximumLength() { + String result = InvocationContext.newInvocationContextId(); + assertEquals( + 38, + result.length(), + "Generated ID should have exactly 38 characters but had: " + result.length()); + } + + @Test + @Tag("boundary") + void newInvocationContextIdPrefixDoesNotContainUppercase() { + String result = InvocationContext.newInvocationContextId(); + assertEquals( + "e-", + result.substring(0, 2), + "Prefix should be lowercase 'e-' and not 'E-' or any uppercase variant"); + } + + @Test + @Tag("boundary") + void newInvocationContextIdUuidPartContainsHyphensAtCorrectPositions() { + String result = InvocationContext.newInvocationContextId(); + // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // After "e-" prefix, hyphens should be at positions: 10, 15, 20, 25 (0-indexed in result) + assertEquals('-', result.charAt(10), "Hyphen expected at position 10"); + assertEquals('-', result.charAt(15), "Hyphen expected at position 15"); + assertEquals('-', result.charAt(20), "Hyphen expected at position 20"); + assertEquals('-', result.charAt(25), "Hyphen expected at position 25"); + } + + @Test + @Tag("boundary") + void newInvocationContextIdDoesNotStartWithUppercaseE() { + String result = InvocationContext.newInvocationContextId(); + assertFalse(result.startsWith("E-"), "Generated ID should not start with uppercase 'E-'"); + } + + @Test + @Tag("boundary") + void newInvocationContextIdIsNotBlank() { + String result = InvocationContext.newInvocationContextId(); + assertFalse(result.isBlank(), "Generated ID should not be blank"); + } + + @Test + @Tag("integration") + void newInvocationContextIdCanBeUsedAsInvocationIdInBuilder() { + String newId = InvocationContext.newInvocationContextId(); + assertNotNull(newId); + assertTrue(newId.startsWith("e-"), "ID used in builder should start with 'e-'"); + // Verifies the generated ID is a valid string that can be used as an invocationId + String uuidPart = newId.substring(2); + assertDoesNotThrow( + () -> UUID.fromString(uuidPart), "UUID part of the ID should be parseable as a UUID"); + } + + @Test + @Tag("integration") + void newInvocationContextIdIsConsistentlyFormattedAcrossMultipleCalls() { + int count = 20; + for (int i = 0; i < count; i++) { + String id = InvocationContext.newInvocationContextId(); + assertNotNull(id, "ID should not be null at iteration " + i); + assertTrue(id.startsWith("e-"), "ID should start with 'e-' at iteration " + i); + assertEquals(38, id.length(), "ID should have length 38 at iteration " + i); + assertTrue( + UUID_PATTERN.matcher(id).matches(), "ID should match UUID pattern at iteration " + i); + } + } + + @Test + @Tag("integration") + void newInvocationContextIdAllUniqueAcrossLargeSet() { + int count = 1000; + Set ids = new HashSet<>(); + for (int i = 0; i < count; i++) { + ids.add(InvocationContext.newInvocationContextId()); + } + assertEquals(count, ids.size(), "All 1000 generated IDs should be unique (no collisions)"); + } + + @Test + @Tag("valid") + void newInvocationContextIdUuidPartIsLowercase() { + String result = InvocationContext.newInvocationContextId(); + String uuidPart = result.substring(EXPECTED_PREFIX.length()); + // UUID.randomUUID().toString() produces lowercase hex + assertEquals( + uuidPart.toLowerCase(), uuidPart, "UUID part of the generated ID should be lowercase"); + } + + @Test + @Tag("valid") + void newInvocationContextIdDoesNotContainWhitespace() { + String result = InvocationContext.newInvocationContextId(); + assertFalse(result.contains(" "), "Generated ID should not contain spaces"); + assertFalse(result.contains("\t"), "Generated ID should not contain tabs"); + assertFalse(result.contains("\n"), "Generated ID should not contain newlines"); + } + + @Test + @Tag("valid") + void newInvocationContextIdPrefixNotRepeated() { + String result = InvocationContext.newInvocationContextId(); + // Make sure the prefix "e-" only appears once at the start + String withoutPrefix = result.substring(EXPECTED_PREFIX.length()); + assertFalse( + withoutPrefix.startsWith("e-"), + "The prefix 'e-' should not be repeated in the generated ID"); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextPluginManagerTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextPluginManagerTest.java new file mode 100755 index 000000000..e1cba0be1 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextPluginManagerTest.java @@ -0,0 +1,440 @@ +/* + * 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=pluginManager_009a0b1a05 +ROOST_METHOD_SIG_HASH=pluginManager_3452d016c7 + +Scenario 1: Verify That pluginManager() Returns the PluginManager Set via Builder + +Details: + TestName: pluginManagerReturnsInstanceSetViaBuilder + Description: Verifies that when a specific PluginManager instance is provided through the builder, + the pluginManager() method returns that exact same instance. +Execution: + Arrange: Create a PluginManager instance. Build an InvocationContext using + InvocationContext.builder().pluginManager(pluginManager).build(). + Act: Call pluginManager() on the constructed InvocationContext. + Assert: Use assertSame() to verify the returned PluginManager is identical (same reference) + to the one provided during construction. +Validation: + This assertion confirms that the pluginManager field is stored and retrieved without any + transformation or wrapping. It is critical that the InvocationContext faithfully returns + the exact PluginManager it was given, ensuring all plugin registrations and tool + configurations are preserved and accessible during the agent invocation lifecycle. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.common.collect.ImmutableSet; +import com.google.genai.types.Content; +import com.google.genai.types.FunctionCall; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextPluginManagerTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private BaseAgent mockAgent; + private Session session; + private RunConfig runConfig; + private PluginManager pluginManager; + @BeforeEach + void setUp() { + session = Session.builder("test-session-id") + .build(); + runConfig = RunConfig.builder().build(); + pluginManager = new PluginManager(); + } + @Test + @Tag("valid") + void pluginManagerReturnsInstanceSetViaBuilder() { + // Arrange + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(expectedPluginManager) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + PluginManager result = context.pluginManager(); + // Assert + assertSame(expectedPluginManager, result, + "pluginManager() should return the exact same PluginManager instance set via builder"); + } + @Test + @Tag("valid") + void pluginManagerReturnsNullWhenNotSet() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + PluginManager result = context.pluginManager(); + // Assert + assertNull(result, "pluginManager() should return null when no PluginManager was set"); + } + @Test + @Tag("valid") + void pluginManagerReturnsSameInstanceAcrossMultipleCalls() { + // Arrange + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(expectedPluginManager) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + PluginManager firstCall = context.pluginManager(); + PluginManager secondCall = context.pluginManager(); + // Assert + assertSame(firstCall, secondCall, + "pluginManager() should return the same instance across multiple calls"); + assertSame(expectedPluginManager, firstCall, + "pluginManager() should return the exact instance set via builder"); + } + @Test + @Tag("valid") + void pluginManagerReturnedInstancePreservesRegisteredPlugins() { + // Arrange + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(expectedPluginManager) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + PluginManager result = context.pluginManager(); + // Assert + assertNotNull(result, "pluginManager() should not return null when a PluginManager was set"); + assertSame(expectedPluginManager, result, + "Returned PluginManager should be the same reference as the one set"); + } + @Test + @Tag("valid") + void pluginManagerReturnedFromCopyOfPreservesOriginal() { + // Arrange + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(expectedPluginManager) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + PluginManager result = copiedContext.pluginManager(); + // Assert + assertSame(expectedPluginManager, result, + "copyOf should preserve the same PluginManager reference"); + assertSame(originalContext.pluginManager(), copiedContext.pluginManager(), + "Original and copied contexts should share the same PluginManager instance"); + } + @Test + @Tag("valid") + void pluginManagerNotNullWhenExplicitlySet() { + // Arrange + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(expectedPluginManager) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act & Assert + assertNotNull(context.pluginManager(), + "pluginManager() should not be null when explicitly set"); + } + @Test + @Tag("valid") + void pluginManagerIsIndependentFromMemoryService() { + // Arrange + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(expectedPluginManager) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + PluginManager result = context.pluginManager(); + // Assert + assertSame(expectedPluginManager, result, + "pluginManager() should be independent from other services like memoryService"); + assertNotNull(context.memoryService(), + "memoryService should still be accessible"); + } + @Test + @Tag("valid") + void pluginManagerReturnedFromCreateMethodWithUserContent() { + // Arrange + PluginManager expectedPluginManager = new PluginManager(); + String invocationId = InvocationContext.newInvocationContextId(); + Content userContent = mock(Content.class); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(expectedPluginManager) + .invocationId(invocationId) + .agent(mockAgent) + .session(session) + .userContent(Optional.of(userContent)) + .runConfig(runConfig) + .build(); + // Act + PluginManager result = context.pluginManager(); + // Assert + assertSame(expectedPluginManager, result, + "pluginManager() should return the same instance regardless of userContent"); + } + @Test + @Tag("valid") + void pluginManagerReturnedFromDeprecatedConstructor() { + // Arrange + PluginManager expectedPluginManager = new PluginManager(); + String invocationId = "test-invocation-id"; + @SuppressWarnings("deprecation") + InvocationContext context = new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + expectedPluginManager, + Optional.empty(), + Optional.empty(), + invocationId, + mockAgent, + session, + Optional.empty(), + runConfig, + false + ); + // Act + PluginManager result = context.pluginManager(); + // Assert + assertSame(expectedPluginManager, result, + "pluginManager() should return the exact instance passed to the deprecated constructor"); + } + @Test + @Tag("valid") + void pluginManagerReturnedFromDeprecatedConstructorWithoutPluginManager() { + // Arrange + String invocationId = "test-invocation-id"; + @SuppressWarnings("deprecation") + InvocationContext context = new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + Optional.empty(), + Optional.empty(), + invocationId, + mockAgent, + session, + Optional.empty(), + runConfig, + false + ); + // Act + PluginManager result = context.pluginManager(); + // Assert + assertNull(result, + "pluginManager() should return null when not passed in deprecated constructor without PluginManager"); + } + @Test + @Tag("integration") + void pluginManagerAccessibleAfterMultipleContextOperations() { + // Arrange + PluginManager expectedPluginManager = new PluginManager(); + String invocationId = InvocationContext.newInvocationContextId(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(expectedPluginManager) + .invocationId(invocationId) + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act - perform multiple operations on the context + context.setEndInvocation(true); + context.branch("test.branch"); + context.agent(mockAgent); + PluginManager result = context.pluginManager(); + // Assert + assertSame(expectedPluginManager, result, + "pluginManager() should remain accessible and unchanged after other context mutations"); + assertTrue(context.endInvocation(), "endInvocation should be set to true"); + assertTrue(context.branch().isPresent(), "branch should be set"); + } + @Test + @Tag("boundary") + void pluginManagerWithMinimalBuilderConfiguration() { + // Arrange - build with only minimum required fields plus pluginManager + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext context = InvocationContext.builder() + .pluginManager(expectedPluginManager) + .build(); + // Act + PluginManager result = context.pluginManager(); + // Assert + assertSame(expectedPluginManager, result, + "pluginManager() should return the set instance even with minimal builder configuration"); + } + @Test + @Tag("boundary") + void pluginManagerDoesNotChangeWhenCopyOfIsInvokedAndOtherFieldsAreModified() { + // Arrange + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(expectedPluginManager) + .invocationId("original-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + // Act - modify copied context's branch + copiedContext.branch("new-branch"); + // Assert + assertSame(expectedPluginManager, copiedContext.pluginManager(), + "Modifying branch should not affect pluginManager reference"); + assertSame(originalContext.pluginManager(), copiedContext.pluginManager(), + "Both original and copied contexts should share the same PluginManager"); + } + @Test + @Tag("valid") + void pluginManagerEqualsCheckIncludesPluginManager() { + // Arrange + PluginManager pluginManager1 = new PluginManager(); + PluginManager pluginManager2 = new PluginManager(); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(pluginManager1) + .invocationId("test-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(pluginManager2) + .invocationId("test-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act & Assert + assertNotEquals(context1, context2, + "Contexts with different PluginManager instances should not be equal"); + assertSame(pluginManager1, context1.pluginManager()); + assertSame(pluginManager2, context2.pluginManager()); + assertNotSame(context1.pluginManager(), context2.pluginManager(), + "Different PluginManager instances should not be the same reference"); + } + @Test + @Tag("valid") + void pluginManagerSameInstanceResultsInEqualContexts() { + // Arrange + PluginManager sharedPluginManager = new PluginManager(); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(sharedPluginManager) + .invocationId("test-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifact \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextRunConfigTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextRunConfigTest.java new file mode 100644 index 000000000..b7bdd462a --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextRunConfigTest.java @@ -0,0 +1,445 @@ +/* + * 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=runConfig_09ff6f346f +ROOST_METHOD_SIG_HASH=runConfig_dbfa69ec19 + +Scenario 1: Return RunConfig When Explicitly Set via Builder + +Details: + TestName: runConfigReturnsExplicitlySetRunConfig + Description: Verifies that the `runConfig()` method returns the exact `RunConfig` instance + that was provided to the builder when constructing the `InvocationContext`. + This ensures the field is correctly stored and returned without modification. +Execution: + Arrange: Create a `RunConfig` instance using `RunConfig.builder().build()` with specific + settings. Build an `InvocationContext` using `InvocationContext.builder()` + and supply the `RunConfig` instance via `.runConfig(runConfig)`. + Act: Call `invocationContext.runConfig()` on the constructed context. + Assert: Use `assertSame()` or `assertEquals()` to verify that the returned `RunConfig` + is the same instance as the one supplied to the builder. +Validation: + The assertion verifies referential equality between the input and returned `RunConfig`, + confirming that the getter faithfully returns exactly what was set — no defensive copying + or transformation occurs. This is important for predictable configuration behavior + throughout an agent invocation lifecycle. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.common.collect.ImmutableSet; +import com.google.genai.types.Content; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.genai.types.FunctionCall; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextRunConfigTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private PluginManager mockPluginManager; + @Mock + private BaseAgent mockAgent; + private Session session; + @BeforeEach + void setUp() { + session = Session.builder("test-session-id") + .appName("test-app") + .userId("test-user") + .build(); + } + // ------------------------------------------------------------------------- + // Scenario 1: Return RunConfig When Explicitly Set via Builder + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void runConfigReturnsExplicitlySetRunConfig() { + // Arrange + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-001") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert + assertSame(runConfig, returnedRunConfig, + "runConfig() should return the exact same RunConfig instance provided to the builder"); + } + // ------------------------------------------------------------------------- + // Additional valid scenarios + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void runConfigReturnsNullWhenNotSet() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-002") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(null) + .build(); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert + assertNull(returnedRunConfig, + "runConfig() should return null when no RunConfig was provided"); + } + @Test + @Tag("valid") + void runConfigReturnsDefaultRunConfig() { + // Arrange + RunConfig defaultRunConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-003") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert + assertNotNull(returnedRunConfig, "runConfig() should not return null for a default RunConfig"); + assertEquals(defaultRunConfig, returnedRunConfig, + "runConfig() should equal the default RunConfig provided"); + } + @Test + @Tag("valid") + void runConfigWithMaxLlmCallsIsReturnedCorrectly() { + // Arrange + RunConfig runConfig = RunConfig.builder() + .setMaxLlmCalls(100) + .build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-004") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert + assertNotNull(returnedRunConfig, "runConfig() should not be null"); + assertEquals(100, returnedRunConfig.maxLlmCalls(), + "runConfig() should return the RunConfig with the correct maxLlmCalls"); + } + @Test + @Tag("valid") + void runConfigReturnsSameInstanceAfterMultipleCalls() { + // Arrange + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-005") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + RunConfig firstCall = context.runConfig(); + RunConfig secondCall = context.runConfig(); + // Assert + assertSame(firstCall, secondCall, + "runConfig() should return the same instance across multiple calls (immutable field)"); + } + @Test + @Tag("valid") + void runConfigIsRetainedAfterCopyOf() { + // Arrange + RunConfig runConfig = RunConfig.builder() + .setMaxLlmCalls(200) + .build(); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-006") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + RunConfig returnedRunConfig = copy.runConfig(); + // Assert + assertSame(runConfig, returnedRunConfig, + "copyOf() should preserve the same RunConfig instance"); + assertEquals(200, returnedRunConfig.maxLlmCalls(), + "Copied context should have the same maxLlmCalls as the original"); + } + @Test + @Tag("valid") + void runConfigIsRetainedInStaticCreateMethod() { + // Arrange + RunConfig runConfig = RunConfig.builder().build(); + Content userContent = Content.fromParts(); + InvocationContext context = InvocationContext.create( + mockSessionService, + mockArtifactService, + "inv-007", + mockAgent, + session, + userContent, + runConfig + ); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert + assertSame(runConfig, returnedRunConfig, + "static create() should store and return the same RunConfig instance"); + } + @Test + @Tag("valid") + void runConfigWithStreamingModeIsReturnedCorrectly() { + // Arrange + RunConfig runConfig = RunConfig.builder() + .setStreamingMode(RunConfig.StreamingMode.SSE) + .build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-008") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert + assertNotNull(returnedRunConfig); + assertEquals(RunConfig.StreamingMode.SSE, returnedRunConfig.streamingMode(), + "runConfig() should return the RunConfig with SSE streaming mode"); + } + @Test + @Tag("valid") + void runConfigWithSaveInputBlobsAsArtifactsSetToTrue() { + // Arrange + RunConfig runConfig = RunConfig.builder() + .setSaveInputBlobsAsArtifacts(true) + .build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-009") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert + assertNotNull(returnedRunConfig); + assertTrue(returnedRunConfig.saveInputBlobsAsArtifacts(), + "runConfig() should return the RunConfig with saveInputBlobsAsArtifacts=true"); + } + @Test + @Tag("boundary") + void runConfigWithMaxLlmCallsOfZero() { + // Arrange + RunConfig runConfig = RunConfig.builder() + .setMaxLlmCalls(0) + .build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-010") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert + assertNotNull(returnedRunConfig); + assertEquals(0, returnedRunConfig.maxLlmCalls(), + "runConfig() should return the RunConfig with maxLlmCalls=0 (boundary value)"); + } + @Test + @Tag("boundary") + void runConfigWithMaxLlmCallsOfOne() { + // Arrange + RunConfig runConfig = RunConfig.builder() + .setMaxLlmCalls(1) + .build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-011") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert + assertNotNull(returnedRunConfig); + assertEquals(1, returnedRunConfig.maxLlmCalls(), + "runConfig() should return the RunConfig with maxLlmCalls=1 (lower boundary value)"); + } + @Test + @Tag("boundary") + void runConfigWithMaxLlmCallsOfIntegerMaxValue() { + // Arrange + RunConfig runConfig = RunConfig.builder() + .setMaxLlmCalls(Integer.MAX_VALUE) + .build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-012") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert + assertNotNull(returnedRunConfig); + assertEquals(Integer.MAX_VALUE, returnedRunConfig.maxLlmCalls(), + "runConfig() should return the RunConfig with maxLlmCalls=Integer.MAX_VALUE (upper boundary)"); + } + @Test + @Tag("integration") + void runConfigIsConsistentWithIncrementLlmCallsCount() throws LlmCallsLimitExceededException { + // Arrange + RunConfig runConfig = RunConfig.builder() + .setMaxLlmCalls(3) + .build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-013") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + RunConfig returnedRunConfig = context.runConfig(); + // Assert: RunConfig is correctly returned + assertNotNull(returnedRunConfig); + assertEquals(3, returnedRunConfig.maxLlmCalls()); + // Verify incrementLlmCallsCount uses the same RunConfig (integration) + assertDoesNotThrow(() -> context.incrementLlmCallsCount(), + "First increment should not throw"); + assertDoesNotThrow(() -> context.incrementLlmCallsCount(), + "Second increment should not throw"); + assertDoesNotThrow(() -> context.incrementLlmCallsCount(), + "Third increment should not throw"); + assertThrows(LlmCallsLimitExceededException.class, () -> context.incrementLlmCallsCount(), + "Fourth increment should throw LlmCallsLimitExceededException"); + } + @Test + @Tag("integration") + void runConfigRemainsUnchangedAfterAgentAndBranchModification() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(50).build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-014") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act: Modify mutable fields + BaseAgent anotherAgent = mock(BaseAgent.class); + context.agent(anotherAgent); + context.branch("agent1.agent2"); + RunConfig returnedRunConfig = context.runConfig(); + // Assert: RunConfig is still the same original instance + assertSame(runConfig, returnedRunConfig, + "runConfig() should not be affected by changes to mutable fields like agent or branch"); + assertEquals(50, returnedRunConfig.maxLlmCalls()); + } + @Test + @Tag("integration") + void runConfigEqualsVerificationBetweenTwoContextsWithSameRunConfig() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(10).build(); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-015a") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-015a") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig( \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextSessionServiceTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextSessionServiceTest.java new file mode 100755 index 000000000..b5174b23b --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextSessionServiceTest.java @@ -0,0 +1,496 @@ +/* + * 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=sessionService_c04b1de6f8 +ROOST_METHOD_SIG_HASH=sessionService_1fa67a6550 + +Scenario 1: Verify that sessionService() returns the exact instance set via the builder + +Details: + TestName: sessionServiceReturnsInstanceSetViaBuilder + Description: Verifies that the sessionService() method returns the exact same BaseSessionService + instance that was provided during construction of InvocationContext via the Builder. + +Execution: + Arrange: Create a mock or concrete implementation of BaseSessionService. Use InvocationContext.builder() + and set the mocked sessionService, along with any other minimally required fields + (such as session, agent, etc.) to construct a valid InvocationContext. + Act: Call invocationContext.sessionService() on the constructed instance. + Assert: Use assertSame() to verify that the returned object is the exact same instance as the one + passed to the builder. + +Validation: + This assertion confirms that the method performs a simple field retrieval without modification, + wrapping, or copying. Since sessionService is declared as a private final field, it must be + initialized once and returned as-is. This test is critical for ensuring that the session service + dependency is correctly wired and accessible throughout the invocation lifecycle. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextSessionServiceTest { + @Mock private BaseSessionService mockSessionService; + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private PluginManager mockPluginManager; + @Mock private BaseAgent mockAgent; + @Mock private Session mockSession; + @Mock private RunConfig mockRunConfig; + @Mock private ResumabilityConfig mockResumabilityConfig; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + when(mockSession.id()).thenReturn("testSessionId"); + when(mockAgent.name()).thenReturn("testAgent"); + } + + private InvocationContext buildBasicContext(BaseSessionService sessionService) { + return InvocationContext.builder() + .sessionService(sessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .runConfig(mockRunConfig) + .build(); + } + + @Test + @Tag("valid") + void sessionServiceReturnsInstanceSetViaBuilder() { + // Arrange + InvocationContext context = buildBasicContext(mockSessionService); + // Act + BaseSessionService result = context.sessionService(); + // Assert + assertSame( + mockSessionService, + result, + "sessionService() should return the exact same instance set via the builder"); + } + + @Test + @Tag("valid") + void sessionServiceReturnsCorrectInstanceWhenMultipleServicesExist() { + // Arrange + BaseSessionService anotherSessionService = mock(BaseSessionService.class); + InvocationContext context1 = buildBasicContext(mockSessionService); + InvocationContext context2 = buildBasicContext(anotherSessionService); + // Act + BaseSessionService result1 = context1.sessionService(); + BaseSessionService result2 = context2.sessionService(); + // Assert + assertSame( + mockSessionService, + result1, + "First context should return the first session service instance"); + assertSame( + anotherSessionService, + result2, + "Second context should return the second session service instance"); + assertNotSame( + result1, + result2, + "Two different contexts should return different session service instances"); + } + + @Test + @Tag("valid") + void sessionServiceIsNotNullWhenSetViaBuilder() { + // Arrange + InvocationContext context = buildBasicContext(mockSessionService); + // Act + BaseSessionService result = context.sessionService(); + // Assert + assertNotNull(result, "sessionService() should not return null when set via builder"); + } + + @Test + @Tag("valid") + void sessionServiceReturnsSameInstanceAfterMultipleCalls() { + // Arrange + InvocationContext context = buildBasicContext(mockSessionService); + // Act + BaseSessionService firstCall = context.sessionService(); + BaseSessionService secondCall = context.sessionService(); + BaseSessionService thirdCall = context.sessionService(); + // Assert + assertSame( + firstCall, + secondCall, + "sessionService() should return the same instance on repeated calls"); + assertSame( + secondCall, + thirdCall, + "sessionService() should return the same instance on repeated calls"); + assertSame(mockSessionService, firstCall, "All calls should return the original instance"); + } + + @Test + @Tag("valid") + void sessionServiceReturnedViaCreateFactoryMethod() { + // Arrange + Content userContent = mock(Content.class); + InvocationContext context = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "test-invocation-id", + mockAgent, + mockSession, + userContent, + mockRunConfig); + // Act + BaseSessionService result = context.sessionService(); + // Assert + assertSame( + mockSessionService, + result, + "sessionService() should return the same instance when created via static factory method"); + } + + @Test + @Tag("valid") + void sessionServiceReturnedViaCreateFactoryMethodWithLiveQueue() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = + InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + liveRequestQueue, + mockRunConfig); + // Act + BaseSessionService result = context.sessionService(); + // Assert + assertSame( + mockSessionService, + result, + "sessionService() should return the same instance when created via live queue factory method"); + } + + @Test + @Tag("valid") + void sessionServiceReturnedViaCopyOf() { + // Arrange + InvocationContext original = buildBasicContext(mockSessionService); + InvocationContext copy = InvocationContext.copyOf(original); + // Act + BaseSessionService originalResult = original.sessionService(); + BaseSessionService copyResult = copy.sessionService(); + // Assert + assertSame( + mockSessionService, + originalResult, + "Original context should return the mock session service"); + assertSame( + mockSessionService, + copyResult, + "Copied context should return the same session service instance as original"); + assertSame( + originalResult, + copyResult, + "Both original and copy should share the same session service instance"); + } + + @Test + @Tag("valid") + void sessionServiceIsNullWhenNotSet() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .runConfig(mockRunConfig) + .build(); + // Act + BaseSessionService result = context.sessionService(); + // Assert + assertNull(result, "sessionService() should return null when not set via builder"); + } + + @Test + @Tag("valid") + void sessionServiceReturnedViaDeprecatedConstructorWithPluginManager() { + // Arrange + String invocationId = "test-invocation-id"; + Optional liveRequestQueue = Optional.empty(); + Optional branch = Optional.of("testBranch"); + Optional userContent = Optional.empty(); + @SuppressWarnings("deprecation") + InvocationContext context = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + liveRequestQueue, + branch, + invocationId, + mockAgent, + mockSession, + userContent, + mockRunConfig, + false); + // Act + BaseSessionService result = context.sessionService(); + // Assert + assertSame( + mockSessionService, + result, + "sessionService() should return same instance set via deprecated constructor"); + } + + @Test + @Tag("valid") + void sessionServiceReturnedViaDeprecatedConstructorWithoutPluginManager() { + // Arrange + String invocationId = "test-invocation-id"; + Optional liveRequestQueue = Optional.empty(); + Optional branch = Optional.empty(); + Optional userContent = Optional.empty(); + @SuppressWarnings("deprecation") + InvocationContext context = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + liveRequestQueue, + branch, + invocationId, + mockAgent, + mockSession, + userContent, + mockRunConfig, + false); + // Act + BaseSessionService result = context.sessionService(); + // Assert + assertSame( + mockSessionService, + result, + "sessionService() should return same instance set via deprecated constructor without plugin manager"); + } + + @Test + @Tag("integration") + void sessionServiceIsConsistentWithOtherContextProperties() { + // Arrange + String invocationId = "integration-invocation-id"; + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .agent(mockAgent) + .session(mockSession) + .invocationId(invocationId) + .runConfig(mockRunConfig) + .build(); + // Act + BaseSessionService sessionService = context.sessionService(); + BaseArtifactService artifactService = context.artifactService(); + BaseMemoryService memoryService = context.memoryService(); + PluginManager pluginManager = context.pluginManager(); + String returnedInvocationId = context.invocationId(); + // Assert + assertSame(mockSessionService, sessionService, "Session service should match"); + assertSame(mockArtifactService, artifactService, "Artifact service should match"); + assertSame(mockMemoryService, memoryService, "Memory service should match"); + assertSame(mockPluginManager, pluginManager, "Plugin manager should match"); + assertEquals(invocationId, returnedInvocationId, "Invocation ID should match"); + } + + @Test + @Tag("integration") + void sessionServiceAccessibleAfterBuilderModifications() { + // Arrange + BaseSessionService anotherSessionService = mock(BaseSessionService.class); + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .runConfig(mockRunConfig) + .build(); + InvocationContext anotherContext = + InvocationContext.builder() + .sessionService(anotherSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("another-invocation-id") + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertSame( + mockSessionService, + context.sessionService(), + "First context session service should be original mock"); + assertSame( + anotherSessionService, + anotherContext.sessionService(), + "Second context session service should be another mock"); + assertNotSame( + context.sessionService(), + anotherContext.sessionService(), + "Two different context instances should have different session services"); + } + + @Test + @Tag("boundary") + void sessionServiceDoesNotInteractWithSessionWhenAccessed() { + // Arrange + InvocationContext context = buildBasicContext(mockSessionService); + // Act + context.sessionService(); + // Assert - verify that accessing sessionService does not trigger any interaction with session + // mock + verify(mockSession, never()).id(); + verify(mockSession, never()).events(); + verify(mockSession, never()).state(); + assertSame(mockSessionService, context.sessionService()); + } + + @Test + @Tag("boundary") + void sessionServiceDoesNotChangeAfterBranchModification() { + // Arrange + InvocationContext context = buildBasicContext(mockSessionService); + BaseSessionService beforeBranchChange = context.sessionService(); + // Act + context.branch("newBranch"); + BaseSessionService afterBranchChange = context.sessionService(); + // Assert + assertSame( + beforeBranchChange, + afterBranchChange, + "sessionService() should return same instance after branch modification"); + assertSame( + mockSessionService, + afterBranchChange, + "sessionService() should still return the original mock after branch change"); + } + + @Test + @Tag("boundary") + void sessionServiceDoesNotChangeAfterEndInvocationModification() { + // Arrange + InvocationContext context = buildBasicContext(mockSessionService); + BaseSessionService beforeModification = context.sessionService(); + // Act + context.setEndInvocation(true); + BaseSessionService afterModification = context.sessionService(); + // Assert + assertSame( + beforeModification, + afterModification, + "sessionService() should return same instance after endInvocation flag change"); + assertSame( + mockSessionService, + afterModification, + "sessionService() should still return original mock after endInvocation modification"); + } + + @Test + @Tag("boundary") + void sessionServiceDoesNotChangeAfterAgentModification() { + // Arrange + InvocationContext context = buildBasicContext(mockSessionService); + BaseSessionService beforeModification = context.sessionService(); + BaseAgent anotherAgent = mock(BaseAgent.class); + when(anotherAgent.name()).thenReturn("anotherAgent"); + // Act + context.agent(anotherAgent); + BaseSessionService afterModification = context.sessionService(); + // Assert + assertSame( + beforeModification, + afterModification, + "sessionService() should return same instance after agent change"); + assertSame( + mockSessionService, + afterModification, + "sessionService() should still return original mock after agent modification"); + } + + @Test + @Tag("valid") + void sessionServiceEqualsCheckForTwoContextsWithSameService() { + // Arrange + InvocationContext context1 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("same-invocation-id") + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("same-invocation-id") + .runConfig(mockRunConfig) + .build(); + // Act + BaseSessionService service1 = context1.sessionService(); + BaseSessionService service2 = context2.sessionService(); + // Assert + assertSame( + service1, + service2, + "Both contexts with same session service should return the identical instance"); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextSessionTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextSessionTest.java new file mode 100755 index 000000000..a25a26119 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextSessionTest.java @@ -0,0 +1,431 @@ +/* + * 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=session_a0f90dafba +ROOST_METHOD_SIG_HASH=session_a2fc7a867d + +Scenario 1: Verify That session() Returns the Session Object Set via Builder + +Details: + TestName: sessionReturnsSessionSetViaBuilder + Description: Verifies that the session() method returns the exact Session object + that was provided to the InvocationContext via the Builder pattern. + This confirms the basic contract of the accessor method. +Execution: + Arrange: Create a mock or stub Session object. Build an InvocationContext using + InvocationContext.builder() and set the session using .session(mockSession). + Call .build() to create the InvocationContext instance. + Act: Call invocationContext.session() on the created InvocationContext instance. + Assert: Use assertEquals or assertSame to verify that the returned Session object + is identical (same reference) to the one that was provided during construction. +Validation: + This assertion confirms that session() returns exactly the same Session instance + injected at construction time without any transformation or wrapping. This is + critical because downstream components such as appName() and userId() rely on + the session field, so any incorrect return value would propagate errors throughout + the invocation lifecycle. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.genai.types.FunctionCall; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nullable; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextSessionTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private PluginManager mockPluginManager; + @Mock + private BaseAgent mockAgent; + @Mock + private Session mockSession; + @Mock + private RunConfig mockRunConfig; + private static final String TEST_INVOCATION_ID = "test-invocation-id-123"; + private static final String TEST_SESSION_ID = "test-session-id-456"; + private static final String TEST_APP_NAME = "testApp"; + private static final String TEST_USER_ID = "testUser"; + @BeforeEach + void setUp() { + when(mockSession.id()).thenReturn(TEST_SESSION_ID); + when(mockSession.appName()).thenReturn(TEST_APP_NAME); + when(mockSession.userId()).thenReturn(TEST_USER_ID); + } + @Test + @Tag("valid") + void sessionReturnsSessionSetViaBuilder() { + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + Session result = invocationContext.session(); + assertSame(mockSession, result, "session() should return the exact same Session instance set via builder"); + } + @Test + @Tag("valid") + void sessionReturnsSameReferenceNotACopy() { + Session specificSession = mock(Session.class); + when(specificSession.id()).thenReturn("specific-session-id"); + when(specificSession.appName()).thenReturn(TEST_APP_NAME); + when(specificSession.userId()).thenReturn(TEST_USER_ID); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(specificSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(specificSession, invocationContext.session()); + assertEquals("specific-session-id", invocationContext.session().id()); + } + @Test + @Tag("valid") + void sessionReturnedIsNotNull() { + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertNotNull(invocationContext.session(), "session() should not return null when session is set"); + } + @Test + @Tag("valid") + void sessionReturnedHasCorrectAppName() { + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + Session returnedSession = invocationContext.session(); + assertEquals(TEST_APP_NAME, returnedSession.appName()); + } + @Test + @Tag("valid") + void sessionReturnedHasCorrectUserId() { + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + Session returnedSession = invocationContext.session(); + assertEquals(TEST_USER_ID, returnedSession.userId()); + } + @Test + @Tag("valid") + void sessionReturnedHasCorrectSessionId() { + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + Session returnedSession = invocationContext.session(); + assertEquals(TEST_SESSION_ID, returnedSession.id()); + } + @Test + @Tag("valid") + void sessionReturnedViaStaticCreateMethodIsSameInstance() { + Content userContent = mock(Content.class); + InvocationContext invocationContext = InvocationContext.create( + mockSessionService, + mockArtifactService, + TEST_INVOCATION_ID, + mockAgent, + mockSession, + userContent, + mockRunConfig + ); + assertSame(mockSession, invocationContext.session()); + } + @Test + @Tag("valid") + void sessionReturnedConsistentlyAcrossMultipleCalls() { + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + Session firstCall = invocationContext.session(); + Session secondCall = invocationContext.session(); + Session thirdCall = invocationContext.session(); + assertSame(firstCall, secondCall, "Multiple calls to session() should return same instance"); + assertSame(secondCall, thirdCall, "Multiple calls to session() should return same instance"); + } + @Test + @Tag("valid") + void sessionReturnedAfterCopyOfPreservesOriginalSession() { + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copied = InvocationContext.copyOf(original); + assertSame(mockSession, copied.session(), "Copied InvocationContext should have the same Session reference"); + } + @Test + @Tag("valid") + void sessionReturnedMatchesSessionUsedForAppName() { + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertEquals(invocationContext.session().appName(), invocationContext.appName()); + } + @Test + @Tag("valid") + void sessionReturnedMatchesSessionUsedForUserId() { + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertEquals(invocationContext.session().userId(), invocationContext.userId()); + } + @Test + @Tag("valid") + void sessionReturnedWithAllOptionalFieldsPresent() { + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + ResumabilityConfig resumabilityConfig = mock(ResumabilityConfig.class); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .liveRequestQueue(Optional.of(liveRequestQueue)) + .branch(Optional.of("main.branch")) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .endInvocation(false) + .resumabilityConfig(resumabilityConfig) + .build(); + assertSame(mockSession, invocationContext.session()); + } + @Test + @Tag("valid") + void sessionReturnedWithMinimalBuilderConfiguration() { + InvocationContext invocationContext = InvocationContext.builder() + .session(mockSession) + .build(); + assertSame(mockSession, invocationContext.session()); + } + @Test + @Tag("valid") + void sessionReturnedWithDifferentSessionInstances() { + Session sessionA = mock(Session.class); + when(sessionA.id()).thenReturn("session-a"); + when(sessionA.appName()).thenReturn("appA"); + when(sessionA.userId()).thenReturn("userA"); + Session sessionB = mock(Session.class); + when(sessionB.id()).thenReturn("session-b"); + when(sessionB.appName()).thenReturn("appB"); + when(sessionB.userId()).thenReturn("userB"); + InvocationContext contextA = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(sessionA) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext contextB = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(sessionB) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(sessionA, contextA.session()); + assertSame(sessionB, contextB.session()); + assertNotSame(contextA.session(), contextB.session()); + } + @Test + @Tag("integration") + void sessionReturnedIsConsistentWithEqualsContract() { + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(context1.session(), context2.session(), + "Two contexts built with same session should return same session reference"); + } + @Test + @Tag("valid") + void sessionReturnedViaDeprecatedConstructorIsSameInstance() { + @SuppressWarnings("deprecation") + InvocationContext invocationContext = new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + Optional.empty(), + Optional.empty(), + TEST_INVOCATION_ID, + mockAgent, + mockSession, + Optional.empty(), + mockRunConfig, + false + ); + assertSame(mockSession, invocationContext.session()); + } + @Test + @Tag("valid") + void sessionReturnedViaSecondDeprecatedConstructorIsSameInstance() { + @SuppressWarnings("deprecation") + InvocationContext invocationContext = new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + Optional.empty(), + Optional.empty(), + TEST_INVOCATION_ID, + mockAgent, + mockSession, + Optional.empty(), + mockRunConfig, + false + ); + assertSame(mockSession, invocationContext.session()); + } + @Test + @Tag("boundary") + void sessionReturnedWhenSessionHasEmptyState() { + Session sessionWithState = mock(Session.class); + when(sessionWithState.id()).thenReturn("empty-state-session"); + when(sessionWithState.appName()).thenReturn(TEST_APP_NAME); + when(sessionWithState.userId()).thenReturn(TEST_USER_ID); + when(sessionWithState.state()).thenReturn(new ConcurrentHashMap<>()); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(sessionWithState) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(sessionWithState, invocationContext.session()); + assertNotNull(invocationContext.session().state()); + assertTrue(invocationContext.session().state().isEmpty()); + } + @Test + @Tag("boundary") + void sessionReturnedWhenSessionHasNoEvents() { + Session sessionWithNoEvents = mock(Session.class); + when(sessionWithNoEvents.id()).thenReturn("no-events-session"); + when(sessionWithNoEvents.appName()).thenReturn(TEST_APP_NAME); + when(sessionWithNoEvents.userId()).thenReturn(TEST_USER_ID); + when(sessionWithNoEvents.events()).thenReturn(new java.util.ArrayList<>()); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextShouldPauseInvocationTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextShouldPauseInvocationTest.java new file mode 100644 index 000000000..5de690964 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextShouldPauseInvocationTest.java @@ -0,0 +1,327 @@ +/* + * 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=shouldPauseInvocation_16da4698a8 +ROOST_METHOD_SIG_HASH=shouldPauseInvocation_4abf425c41 + +Scenario 1: Non-Resumable Invocation Context Always Returns False + +Details: + TestName: shouldReturnFalseWhenInvocationContextIsNotResumable + Description: Verifies that when the InvocationContext is configured with a non-resumable ResumabilityConfig, + the method returns false immediately, regardless of whether the event contains long-running tool IDs + or matching function calls. This tests the first guard condition in the method. +Execution: + Arrange: + - Create a mock Event object. + - Create a ResumabilityConfig configured so that isResumable() returns false. + - Build an InvocationContext using InvocationContext.builder() with the non-resumable ResumabilityConfig. + - Configure the mock Event to return an Optional containing a non-empty ImmutableSet of long-running tool IDs. + - Configure the mock Event to return a list with at least one FunctionCall whose id matches one of the long-running tool IDs. + Act: + - Call invocationContext.shouldPauseInvocation(event). + Assert: + - Assert that the returned value is false using assertFalse(). +Validation: + This assertion confirms that the non-resumable guard is the first and highest priority check. + When the invocation is not resumable, pausing has no meaning, so the method correctly short-circuits + and returns false. This is critical to prevent incorrect pause states in non-resumable invocations. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.genai.types.Content; +import com.google.genai.types.FunctionCall; +import java.util.Optional; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextShouldPauseInvocationTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private PluginManager mockPluginManager; + @Mock + private BaseAgent mockAgent; + @Mock + private Session mockSession; + @Mock + private ResumabilityConfig mockResumabilityConfig; + @Mock + private RunConfig mockRunConfig; + @Mock + private Event mockEvent; + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + when(mockSession.id()).thenReturn("testSession"); + } + private InvocationContext buildInvocationContext(ResumabilityConfig resumabilityConfig) { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(resumabilityConfig) + .build(); + } + @Test + @Tag("invalid") + void shouldReturnFalseWhenInvocationContextIsNotResumable() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + Set longRunningToolIds = ImmutableSet.of("tool-id-1", "tool-id-2"); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(longRunningToolIds)); + FunctionCall mockFunctionCall = mock(FunctionCall.class); + when(mockFunctionCall.id()).thenReturn(Optional.of("tool-id-1")); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(mockFunctionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "Expected shouldPauseInvocation to return false when the context is not resumable"); + } + @Test + @Tag("invalid") + void shouldReturnFalseWhenLongRunningToolIdsIsEmpty() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of())); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "Expected shouldPauseInvocation to return false when longRunningToolIds is empty"); + } + @Test + @Tag("invalid") + void shouldReturnFalseWhenLongRunningToolIdsIsAbsent() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.empty()); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "Expected shouldPauseInvocation to return false when longRunningToolIds is absent"); + } + @Test + @Tag("valid") + void shouldReturnTrueWhenResumableAndFunctionCallIdMatchesLongRunningToolId() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + String matchingToolId = "long-running-tool-id"; + Set longRunningToolIds = ImmutableSet.of(matchingToolId); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(longRunningToolIds)); + FunctionCall mockFunctionCall = mock(FunctionCall.class); + when(mockFunctionCall.id()).thenReturn(Optional.of(matchingToolId)); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(mockFunctionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertTrue(result, "Expected shouldPauseInvocation to return true when function call ID matches a long-running tool ID"); + } + @Test + @Tag("invalid") + void shouldReturnFalseWhenNoFunctionCallIdMatchesLongRunningToolId() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + Set longRunningToolIds = ImmutableSet.of("long-running-tool-id"); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(longRunningToolIds)); + FunctionCall mockFunctionCall = mock(FunctionCall.class); + when(mockFunctionCall.id()).thenReturn(Optional.of("non-matching-tool-id")); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(mockFunctionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "Expected shouldPauseInvocation to return false when no function call ID matches a long-running tool ID"); + } + @Test + @Tag("invalid") + void shouldReturnFalseWhenFunctionCallsListIsEmpty() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + Set longRunningToolIds = ImmutableSet.of("long-running-tool-id"); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(longRunningToolIds)); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of()); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "Expected shouldPauseInvocation to return false when function calls list is empty"); + } + @Test + @Tag("invalid") + void shouldReturnFalseWhenFunctionCallHasNoId() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + Set longRunningToolIds = ImmutableSet.of("long-running-tool-id"); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(longRunningToolIds)); + FunctionCall mockFunctionCall = mock(FunctionCall.class); + when(mockFunctionCall.id()).thenReturn(Optional.empty()); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(mockFunctionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "Expected shouldPauseInvocation to return false when function call has no ID"); + } + @Test + @Tag("valid") + void shouldReturnTrueWhenOnlyOneFunctionCallIdMatchesAmongMultiple() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + String matchingToolId = "long-running-tool-id"; + Set longRunningToolIds = ImmutableSet.of(matchingToolId); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(longRunningToolIds)); + FunctionCall nonMatchingCall = mock(FunctionCall.class); + when(nonMatchingCall.id()).thenReturn(Optional.of("non-matching-id")); + FunctionCall matchingCall = mock(FunctionCall.class); + when(matchingCall.id()).thenReturn(Optional.of(matchingToolId)); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(nonMatchingCall, matchingCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertTrue(result, "Expected shouldPauseInvocation to return true when at least one function call ID matches a long-running tool ID"); + } + @Test + @Tag("boundary") + void shouldReturnFalseWhenNonResumableEvenIfAllConditionsWouldOtherwiseMatch() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + // Even though we set up matching long running tools and function calls, it should return false + // because isResumable() is false. We don't even stub the event in this case to + // verify the short circuit. + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "Expected shouldPauseInvocation to return false for a non-resumable context, regardless of event content"); + } + @Test + @Tag("valid") + void shouldReturnTrueWhenMultipleLongRunningToolIdsAndOneFunctionCallMatches() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + String matchingToolId = "tool-id-2"; + Set longRunningToolIds = ImmutableSet.of("tool-id-1", matchingToolId, "tool-id-3"); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(longRunningToolIds)); + FunctionCall mockFunctionCall = mock(FunctionCall.class); + when(mockFunctionCall.id()).thenReturn(Optional.of(matchingToolId)); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(mockFunctionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertTrue(result, "Expected shouldPauseInvocation to return true when function call ID matches one of multiple long-running tool IDs"); + } + @Test + @Tag("boundary") + void shouldReturnFalseWhenLongRunningToolIdsExistsButAllFunctionCallsHaveNoId() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + Set longRunningToolIds = ImmutableSet.of("tool-id-1"); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(longRunningToolIds)); + FunctionCall callWithNoId1 = mock(FunctionCall.class); + when(callWithNoId1.id()).thenReturn(Optional.empty()); + FunctionCall callWithNoId2 = mock(FunctionCall.class); + when(callWithNoId2.id()).thenReturn(Optional.empty()); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(callWithNoId1, callWithNoId2)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "Expected shouldPauseInvocation to return false when all function calls have no ID"); + } + @Test + @Tag("boundary") + void shouldReturnFalseWhenAllFunctionCallsNonMatchingAndNoIdMixed() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + Set longRunningToolIds = ImmutableSet.of("tool-id-1"); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(longRunningToolIds)); + FunctionCall callWithNoId = mock(FunctionCall.class); + when(callWithNoId.id()).thenReturn(Optional.empty()); + FunctionCall callWithNonMatchingId = mock(FunctionCall.class); + when(callWithNonMatchingId.id()).thenReturn(Optional.of("tool-id-99")); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(callWithNoId, callWithNonMatchingId)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "Expected shouldPauseInvocation to return false when no function call ID matches"); + } + @Test + @Tag("integration") + void shouldCorrectlyHandleResumableContextWithMatchingFunctionCall() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + String toolId = "integration-tool-id"; + Set longRunningToolIds = ImmutableSet.of(toolId); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(longRunningToolIds)); + FunctionCall matchingFunctionCall = mock(FunctionCall.class); + when(matchingFunctionCall.id()).thenReturn(Optional.of(toolId)); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(matchingFunctionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mock \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextUserContentTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextUserContentTest.java new file mode 100755 index 000000000..44af9eeec --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextUserContentTest.java @@ -0,0 +1,446 @@ +/* + * 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=userContent_84fbf4a756 +ROOST_METHOD_SIG_HASH=userContent_7428a149e0 + +Scenario 1: Return Present Optional When User Content Is Set With a Content Object + +Details: + TestName: userContentReturnsPresentOptionalWhenContentIsProvided + Description: Verifies that the `userContent()` method returns a non-empty Optional when + a valid `Content` object is supplied to the builder during construction + of the `InvocationContext`. + +Execution: + Arrange: Create a mock or stub `Content` object. Build an `InvocationContext` using + `InvocationContext.builder()` with `.userContent(contentObject)` set to + the created `Content` instance. Also set the minimum required fields such as + `sessionService`, `artifactService`, `session`, and `agent` using mocks. + Act: Call `invocationContext.userContent()` on the constructed context. + Assert: Use `assertTrue(invocationContext.userContent().isPresent())` to confirm + that the returned Optional contains a value. + +Validation: + This assertion confirms that when a `Content` object is passed via the builder's + `.userContent(Content)` method, the `userContent()` accessor correctly wraps it in + a non-empty Optional. This is critical for downstream components that rely on the + presence of user-provided content to drive agent invocation logic. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextUserContentTest { + @Mock private BaseSessionService mockSessionService; + @Mock private BaseArtifactService mockArtifactService; + @Mock private BaseMemoryService mockMemoryService; + @Mock private PluginManager mockPluginManager; + @Mock private Session mockSession; + @Mock private BaseAgent mockAgent; + @Mock private Content mockContent; + @Mock private RunConfig mockRunConfig; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + when(mockSession.id()).thenReturn("testSessionId"); + } + + @Test + @Tag("valid") + void userContentReturnsPresentOptionalWhenContentIsProvided() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(mockContent) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = context.userContent(); + // Assert + assertTrue( + result.isPresent(), + "userContent() should return a non-empty Optional when content is provided"); + } + + @Test + @Tag("valid") + void userContentReturnsCorrectContentObjectWhenSet() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(mockContent) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = context.userContent(); + // Assert + assertTrue(result.isPresent()); + assertSame( + mockContent, + result.get(), + "userContent() should return the exact same Content object that was set"); + } + + @Test + @Tag("valid") + void userContentReturnsEmptyOptionalWhenNullContentPassedToOptionalBuilder() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = context.userContent(); + // Assert + assertFalse( + result.isPresent(), + "userContent() should return an empty Optional when Optional.empty() is set"); + } + + @Test + @Tag("valid") + void userContentReturnsEmptyOptionalWhenNotSet() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = context.userContent(); + // Assert + assertFalse( + result.isPresent(), + "userContent() should return an empty Optional when no content was set"); + } + + @Test + @Tag("valid") + void userContentReturnsOptionalContainingContentWhenSetViaOptionalWrapper() { + // Arrange + Optional optionalContent = Optional.of(mockContent); + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(optionalContent) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = context.userContent(); + // Assert + assertTrue( + result.isPresent(), + "userContent() should return a present Optional when set via Optional.of()"); + assertSame( + mockContent, result.get(), "userContent() should return the content wrapped in Optional"); + } + + @Test + @Tag("valid") + void userContentReturnsPresentOptionalWhenCreatedViaStaticCreateMethod() { + // Arrange + InvocationContext context = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "test-invocation-id", + mockAgent, + mockSession, + mockContent, + mockRunConfig); + // Act + Optional result = context.userContent(); + // Assert + assertTrue( + result.isPresent(), + "userContent() should return a present Optional when created via static create()"); + assertSame( + mockContent, result.get(), "userContent() should return the expected Content object"); + } + + @Test + @Tag("valid") + void userContentReturnsEmptyOptionalWhenNullPassedToStaticCreateMethod() { + // Arrange + InvocationContext context = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "test-invocation-id", + mockAgent, + mockSession, + null, + mockRunConfig); + // Act + Optional result = context.userContent(); + // Assert + assertFalse( + result.isPresent(), + "userContent() should return empty Optional when null is passed to static create()"); + } + + @Test + @Tag("valid") + void userContentReturnsSameOptionalTypeNotNull() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(mockContent) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = context.userContent(); + // Assert + assertNotNull(result, "userContent() should never return null; it should return Optional"); + } + + @Test + @Tag("valid") + void userContentReturnsNotNullWhenContentIsNotSet() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = context.userContent(); + // Assert + assertNotNull( + result, + "userContent() should never return null even when no content is set; should return Optional.empty()"); + } + + @Test + @Tag("integration") + void userContentIsPreservedAfterCopyOf() { + // Arrange + InvocationContext original = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(mockContent) + .runConfig(mockRunConfig) + .build(); + // Act + InvocationContext copied = InvocationContext.copyOf(original); + Optional result = copied.userContent(); + // Assert + assertTrue(result.isPresent(), "userContent() should be preserved in copied InvocationContext"); + assertSame( + mockContent, result.get(), "Copied context should reference the same Content object"); + } + + @Test + @Tag("integration") + void userContentEmptyIsPreservedAfterCopyOf() { + // Arrange + InvocationContext original = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + InvocationContext copied = InvocationContext.copyOf(original); + Optional result = copied.userContent(); + // Assert + assertFalse( + result.isPresent(), "Empty userContent should be preserved in copied InvocationContext"); + } + + @Test + @Tag("boundary") + void userContentReturnsPresentOptionalWhenExplicitlySetToOptionalOfContent() { + // Arrange + Content content = mock(Content.class); + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("boundary-test-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(content)) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = context.userContent(); + // Assert + assertTrue( + result.isPresent(), + "userContent should be present when explicitly set as Optional.of(content)"); + assertEquals(content, result.get(), "userContent should return the exact content set"); + } + + @Test + @Tag("boundary") + void userContentReturnsEmptyOptionalWhenBuiltWithoutSetting() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("boundary-no-content-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = context.userContent(); + // Assert + assertEquals( + Optional.empty(), result, "userContent should be Optional.empty() when not explicitly set"); + } + + @Test + @Tag("valid") + void userContentGetDoesNotThrowWhenPresent() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(mockContent) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertDoesNotThrow( + () -> { + Optional result = context.userContent(); + result.get(); + }, + "Calling .get() on userContent() should not throw when content is present"); + } + + @Test + @Tag("valid") + void userContentIsConsistentAcrossMultipleCalls() { + // Arrange + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(mockContent) + .runConfig(mockRunConfig) + .build(); + // Act + Optional firstCall = context.userContent(); + Optional secondCall = context.userContent(); + // Assert + assertEquals( + firstCall, secondCall, "userContent() should return the same value on multiple calls"); + assertSame( + firstCall.get(), secondCall.get(), "Both calls should return the same Content reference"); + } + + @Test + @Tag("valid") + void userContentReturnsContentSetViaDirectContentBuilder() { + // Arrange + Content directContent = mock(Content.class); + InvocationContext context = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("direct-content-id") + .agent(mockAgent) + .session(mockSession) + .userContent(directContent) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = context.userContent(); + // Assert + assertTrue( + result.isPresent(), + "userContent() should be present when set via .userContent(Content) builder method"); + assertSame(directContent, result.get(), "userContent() should return the direct content set"); + } +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextUserIdTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextUserIdTest.java new file mode 100644 index 000000000..56a7a68af --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextUserIdTest.java @@ -0,0 +1,468 @@ +/* + * 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=userId_f2e833fd39 +ROOST_METHOD_SIG_HASH=userId_5053f72666 + +Scenario 1: Return User ID from Session When User ID is a Standard Non-Empty String + +Details: + TestName: userIdReturnsStandardNonEmptyStringFromSession + Description: Verifies that the `userId()` method correctly delegates to `session.userId()` and + returns a standard, non-empty user ID string that was set in the session. + +Execution: + Arrange: + - Create a mock `Session` object. + - Configure the mock `Session` so that `session.userId()` returns a standard user ID + string, e.g., "user-12345". + - Build an `InvocationContext` instance using `InvocationContext.builder()`, setting + the mocked `session` via `.session(mockSession)`, along with any other required fields + such as `sessionService` and `artifactService`. + Act: + - Invoke `invocationContext.userId()` on the built `InvocationContext` instance. + Assert: + - Use `assertEquals("user-12345", invocationContext.userId())` to verify the result. + +Validation: + - The assertion confirms that `userId()` returns the exact string provided by `session.userId()`. + - This validates that the method correctly delegates to the session object without any + transformation, which is crucial for routing, authorization, and audit-trail logic + that depends on accurately identifying the user. + +*/ + +// ********RoostGPT******** + +```java +package com.google.adk.agents; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.adk.artifacts.BaseArtifactService; +import com.google.adk.events.Event; +import com.google.adk.flows.llmflows.ResumabilityConfig; +import com.google.adk.memory.BaseMemoryService; +import com.google.adk.models.LlmCallsLimitExceededException; +import com.google.adk.plugins.PluginManager; +import com.google.adk.sessions.BaseSessionService; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.genai.types.FunctionCall; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextUserIdTest { + @Mock + private BaseSessionService mockSessionService; + @Mock + private BaseArtifactService mockArtifactService; + @Mock + private BaseMemoryService mockMemoryService; + @Mock + private PluginManager mockPluginManager; + @Mock + private Session mockSession; + @Mock + private BaseAgent mockAgent; + private static final String DEFAULT_INVOCATION_ID = "test-invocation-id-001"; + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("test-app"); + when(mockSession.id()).thenReturn("test-session-id"); + } + @Test + @Tag("valid") + void userIdReturnsStandardNonEmptyStringFromSession() { + // Arrange + String expectedUserId = "user-12345"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + } + @Test + @Tag("valid") + void userIdReturnsAlphanumericStringFromSession() { + // Arrange + String expectedUserId = "abc123XYZ"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + assertNotNull(result); + } + @Test + @Tag("valid") + void userIdReturnsEmailFormatStringFromSession() { + // Arrange + String expectedUserId = "user@example.com"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + } + @Test + @Tag("valid") + void userIdReturnsUuidFormatStringFromSession() { + // Arrange + String expectedUserId = "550e8400-e29b-41d4-a716-446655440000"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + } + @Test + @Tag("boundary") + void userIdReturnsSingleCharacterUserIdFromSession() { + // Arrange + String expectedUserId = "a"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + assertEquals(1, result.length()); + } + @Test + @Tag("boundary") + void userIdReturnsEmptyStringFromSession() { + // Arrange + String expectedUserId = ""; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + @Test + @Tag("boundary") + void userIdReturnsVeryLongStringFromSession() { + // Arrange + String expectedUserId = "u".repeat(1000); + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + assertEquals(1000, result.length()); + } + @Test + @Tag("invalid") + void userIdReturnsNullWhenSessionReturnsNull() { + // Arrange + when(mockSession.userId()).thenReturn(null); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertNull(result); + } + @Test + @Tag("valid") + void userIdDelegatesCallToSessionObject() { + // Arrange + String expectedUserId = "delegate-test-user"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + invocationContext.userId(); + // Assert + verify(mockSession, times(1)).userId(); + } + @Test + @Tag("valid") + void userIdReturnsConsistentResultOnMultipleCalls() { + // Arrange + String expectedUserId = "consistent-user-id"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result1 = invocationContext.userId(); + String result2 = invocationContext.userId(); + String result3 = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result1); + assertEquals(expectedUserId, result2); + assertEquals(expectedUserId, result3); + assertEquals(result1, result2); + assertEquals(result2, result3); + } + @Test + @Tag("valid") + void userIdReturnsStringWithSpecialCharactersFromSession() { + // Arrange + String expectedUserId = "user_name-with.special+chars@domain.org"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + } + @Test + @Tag("integration") + void userIdMatchesSessionUserIdAfterCopyOf() { + // Arrange + String expectedUserId = "integration-user-001"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + // Act + String originalUserId = originalContext.userId(); + String copiedUserId = copiedContext.userId(); + // Assert + assertEquals(expectedUserId, originalUserId); + assertEquals(expectedUserId, copiedUserId); + assertEquals(originalUserId, copiedUserId); + } + @Test + @Tag("integration") + void userIdIsNotAffectedByBranchChange() { + // Arrange + String expectedUserId = "user-branch-test"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + invocationContext.branch("agentA.agentB"); + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + verify(mockSession, atLeastOnce()).userId(); + } + @Test + @Tag("integration") + void userIdIsNotAffectedByEndInvocationChange() { + // Arrange + String expectedUserId = "user-end-invocation-test"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + invocationContext.setEndInvocation(true); + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + } + @Test + @Tag("valid") + void userIdReturnsStringWithNumericOnlyValue() { + // Arrange + String expectedUserId = "123456789"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + assertNotNull(result); + } + @Test + @Tag("valid") + void userIdReturnsStringWithWhitespaceFromSession() { + // Arrange + String expectedUserId = "user with spaces"; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + } + @Test + @Tag("valid") + void userIdDoesNotTransformOrModifyReturnValue() { + // Arrange + String expectedUserId = " trimTest "; + when(mockSession.userId()).thenReturn(expectedUserId); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(DEFAULT_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + String result = invocationContext.userId(); + // Assert + assertEquals(expectedUserId, result); + assertEquals(" trimTest ", result); + } + @Test + @Tag("integration") + void userIdIsAccessibleAfterCreateMethod() { + // Arrange + String expectedUserId = "create-method-user"; + when(mockSession.userId()).thenReturn \ No newline at end of file diff --git a/pom.xml b/pom.xml old mode 100644 new mode 100755 index 6009c7316..24fd6ece6 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,4 @@ - + - + 4.0.0 - com.google.adk google-adk-parent - 0.4.1-SNAPSHOT + 0.4.1-SNAPSHOT + pom - Google Agent Development Kit Maven Parent POM https://github.com/google/adk-java Google Agent Development Kit (ADK) for Java - core dev @@ -39,12 +35,10 @@ a2a a2a/webservice - 17 ${java.version} UTF-8 - 1.11.0 3.4.1 1.49.0 @@ -73,7 +67,6 @@ 3.9.0 5.4.3 - @@ -112,7 +105,6 @@ pom import - com.anthropic @@ -274,9 +266,21 @@ assertj-core ${assertj.version} + + org.mockito + mockito-junit-jupiter + 2.23.4 + test + + + + io.spring.javaformat + spring-javaformat-formatter + 0.0.40 + + - @@ -324,8 +328,7 @@ plain - + **/*Test.java @@ -469,6 +472,36 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 3.2.5 + + testReport + + + + + org.apache.maven.plugins + maven-site-plugin + 2.1 + + testReport + + + + + io.spring.javaformat + spring-javaformat-maven-plugin + 0.0.40 + + @@ -528,7 +561,6 @@ - The Apache License, Version 2.0 @@ -558,4 +590,19 @@ https://central.sonatype.com/repository/maven-snapshots/ + + + org.mockito + mockito-junit-jupiter + 2.23.4 + test + + + + io.spring.javaformat + spring-javaformat-formatter + 0.0.40 + + + \ No newline at end of file