From 050fdb82a7af192fae17f0aa9f15ecf3e6fd9e33 Mon Sep 17 00:00:00 2001 From: roost-io <8110509+mgdevstack@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:27:56 +0530 Subject: [PATCH] Unit test generated by RoostGPT Using AI Model global.anthropic.claude-sonnet-4-6 --- core/pom.xml | 21 +- ...cationContextActiveStreamingToolsTest.java | 381 ++++++++++ .../agents/InvocationContextAgentTest.java | 416 +++++++++++ .../agents/InvocationContextAppNameTest.java | 538 ++++++++++++++ .../InvocationContextArtifactServiceTest.java | 638 ++++++++++++++++ .../InvocationContextBranch941Test.java | 461 ++++++++++++ .../agents/InvocationContextBranchTest.java | 498 +++++++++++++ .../agents/InvocationContextBuilderTest.java | 681 +++++++++++++++++ .../agents/InvocationContextCopyOfTest.java | 464 ++++++++++++ .../agents/InvocationContextCreate98Test.java | 386 ++++++++++ .../agents/InvocationContextCreateTest.java | 447 +++++++++++ .../InvocationContextEndInvocationTest.java | 487 ++++++++++++ .../agents/InvocationContextEqualsTest.java | 471 ++++++++++++ .../agents/InvocationContextHashCodeTest.java | 478 ++++++++++++ ...tionContextIncrementLlmCallsCountTest.java | 318 ++++++++ .../InvocationContextInvocationIdTest.java | 429 +++++++++++ .../InvocationContextIsResumableTest.java | 457 ++++++++++++ ...InvocationContextLiveRequestQueueTest.java | 428 +++++++++++ .../InvocationContextMemoryServiceTest.java | 363 +++++++++ ...tionContextNewInvocationContextIdTest.java | 261 +++++++ .../InvocationContextPluginManagerTest.java | 380 ++++++++++ .../InvocationContextRunConfigTest.java | 470 ++++++++++++ .../InvocationContextSessionServiceTest.java | 458 ++++++++++++ .../agents/InvocationContextSessionTest.java | 691 ++++++++++++++++++ ...ationContextShouldPauseInvocationTest.java | 377 ++++++++++ .../InvocationContextUserContentTest.java | 448 ++++++++++++ .../agents/InvocationContextUserIdTest.java | 604 +++++++++++++++ pom.xml | 79 +- 28 files changed, 12106 insertions(+), 24 deletions(-) create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextActiveStreamingToolsTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextAgentTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextAppNameTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextArtifactServiceTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextBranch941Test.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextBranchTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextBuilderTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextCopyOfTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextCreate98Test.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextCreateTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextEndInvocationTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextEqualsTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextHashCodeTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextIncrementLlmCallsCountTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextInvocationIdTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextIsResumableTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextLiveRequestQueueTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextMemoryServiceTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextNewInvocationContextIdTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextPluginManagerTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextRunConfigTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextSessionServiceTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextSessionTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextShouldPauseInvocationTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextUserContentTest.java create mode 100644 core/src/test/java/com/google/adk/agents/InvocationContextUserIdTest.java diff --git a/core/pom.xml b/core/pom.xml index fe65715f3..7d2032c7e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,4 +1,4 @@ - + 4.0.0 - com.google.adk google-adk-parent - 0.4.1-SNAPSHOT + 0.4.1-SNAPSHOT + - google-adk Agent Development Kit Agent Development Kit: an open-source, code-first toolkit designed to simplify building, evaluating, and deploying advanced AI agents anywhere. - - - com.anthropic @@ -201,6 +197,15 @@ maven-compiler-plugin + + io.spring.javaformat + spring-javaformat-maven-plugin + 0.0.40 + + + + + - + \ No newline at end of file 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 100644 index 000000000..97c81bbe1 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextActiveStreamingToolsTest.java @@ -0,0 +1,381 @@ +/* + * 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 activeStreamingTools Returns an Empty Map on Fresh InvocationContext + +Details: + TestName: activeStreamingToolsReturnsEmptyMapOnFreshContext + Description: Verify that when a new InvocationContext is created using the builder without adding any streaming tools, the activeStreamingTools() method returns an empty (non-null) map. This checks the default initialization state of the internal ConcurrentHashMap. + +Execution: + Arrange: + - Create a mock or stub for BaseSessionService, BaseArtifactService, and Session. + - Build an InvocationContext using InvocationContext.builder() providing the minimum required fields (sessionService, artifactService, session). + Act: + - Call invocationContext.activeStreamingTools(). + Assert: + - Assert that the returned map is not null. + - Assert that the returned map is empty (size == 0). + +Validation: + The test confirms that the internal ConcurrentHashMap is properly initialized to an empty state on object construction. This is critical to ensure no stale or default data leaks into a fresh invocation context, maintaining data isolation between invocations. + +*/ + +// ********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.errorprone.annotations.CanIgnoreReturnValue; +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.common.collect.ImmutableSet; +import com.google.errorprone.annotations.InlineMe; +import com.google.genai.types.FunctionCall; + +@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 RunConfig mockRunConfig; + + @Mock + private ActiveStreamingTool mockActiveStreamingTool; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + when(mockSession.id()).thenReturn("testSession"); + } + + private InvocationContext buildMinimalInvocationContext() { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .runConfig(mockRunConfig) + .build(); + } + + @Test + @Tag("valid") + void activeStreamingToolsReturnsEmptyMapOnFreshContext() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + // Act + Map result = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(result, "activeStreamingTools map should not be null"); + assertTrue(result.isEmpty(), "activeStreamingTools map should be empty on fresh context"); + assertEquals(0, result.size(), "activeStreamingTools map size should be 0 on fresh context"); + } + + @Test + @Tag("valid") + void activeStreamingToolsReturnsSameMapInstanceAcrossCalls() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + // Act + Map firstCall = invocationContext.activeStreamingTools(); + Map secondCall = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(firstCall, "First call should not be null"); + assertNotNull(secondCall, "Second call should not be null"); + assertSame(firstCall, secondCall, "Both calls should return the same map instance"); + } + + @Test + @Tag("valid") + void activeStreamingToolsReturnsNonNullMap() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + // Act + Map result = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(result, "activeStreamingTools should never return null"); + } + + @Test + @Tag("valid") + void activeStreamingToolsReflectsAddedToolsAfterPuttingInMap() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + String toolId = "tool-123"; + // Act + invocationContext.activeStreamingTools().put(toolId, mockActiveStreamingTool); + Map result = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(result, "Map should not be null"); + assertFalse(result.isEmpty(), "Map should not be empty after adding a tool"); + assertEquals(1, result.size(), "Map should have exactly one entry"); + assertTrue(result.containsKey(toolId), "Map should contain the added tool key"); + assertEquals(mockActiveStreamingTool, result.get(toolId), "Map should return the correct tool for the key"); + } + + @Test + @Tag("valid") + void activeStreamingToolsSupportsMultipleToolsAddedToMap() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + ActiveStreamingTool mockTool1 = mock(ActiveStreamingTool.class); + ActiveStreamingTool mockTool2 = mock(ActiveStreamingTool.class); + ActiveStreamingTool mockTool3 = mock(ActiveStreamingTool.class); + // Act + invocationContext.activeStreamingTools().put("tool-1", mockTool1); + invocationContext.activeStreamingTools().put("tool-2", mockTool2); + invocationContext.activeStreamingTools().put("tool-3", mockTool3); + Map result = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(result, "Map should not be null"); + assertEquals(3, result.size(), "Map should contain exactly 3 tools"); + assertTrue(result.containsKey("tool-1"), "Map should contain tool-1"); + assertTrue(result.containsKey("tool-2"), "Map should contain tool-2"); + assertTrue(result.containsKey("tool-3"), "Map should contain tool-3"); + assertEquals(mockTool1, result.get("tool-1"), "Map should return the correct tool for tool-1"); + assertEquals(mockTool2, result.get("tool-2"), "Map should return the correct tool for tool-2"); + assertEquals(mockTool3, result.get("tool-3"), "Map should return the correct tool for tool-3"); + } + + @Test + @Tag("valid") + void activeStreamingToolsReflectsRemovalOfTool() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + String toolId = "tool-to-remove"; + invocationContext.activeStreamingTools().put(toolId, mockActiveStreamingTool); + // Act + invocationContext.activeStreamingTools().remove(toolId); + Map result = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(result, "Map should not be null after removal"); + assertTrue(result.isEmpty(), "Map should be empty after removing the only tool"); + assertFalse(result.containsKey(toolId), "Map should not contain the removed tool key"); + } + + @Test + @Tag("valid") + void activeStreamingToolsReturnsMutableMap() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + // Act & Assert - verify the map is mutable by putting and removing entries + Map result = invocationContext.activeStreamingTools(); + assertNotNull(result, "Map should not be null"); + assertDoesNotThrow(() -> result.put("test-key", mockActiveStreamingTool), + "Should be able to put entries into the map"); + assertDoesNotThrow(() -> result.remove("test-key"), "Should be able to remove entries from the map"); + } + + @Test + @Tag("integration") + void activeStreamingToolsIsPreservedInCopyOf() { + // Arrange + InvocationContext original = buildMinimalInvocationContext(); + String toolId = "tool-original"; + original.activeStreamingTools().put(toolId, mockActiveStreamingTool); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + Map copiedTools = copy.activeStreamingTools(); + // Assert + assertNotNull(copiedTools, "Copied map should not be null"); + assertFalse(copiedTools.isEmpty(), "Copied map should not be empty"); + assertEquals(1, copiedTools.size(), "Copied map should have 1 entry"); + assertTrue(copiedTools.containsKey(toolId), "Copied map should contain the original tool key"); + assertEquals(mockActiveStreamingTool, copiedTools.get(toolId), "Copied map should have the same tool instance"); + } + + @Test + @Tag("integration") + void activeStreamingToolsModificationOnCopyDoesNotAffectOriginal() { + // Arrange + InvocationContext original = buildMinimalInvocationContext(); + String originalToolId = "original-tool"; + original.activeStreamingTools().put(originalToolId, mockActiveStreamingTool); + InvocationContext copy = InvocationContext.copyOf(original); + // Act - modify the copy's map + ActiveStreamingTool newTool = mock(ActiveStreamingTool.class); + copy.activeStreamingTools().put("new-tool-in-copy", newTool); + copy.activeStreamingTools().remove(originalToolId); + // Assert - original should still have its original tool + Map originalTools = original.activeStreamingTools(); + assertNotNull(originalTools, "Original map should not be null"); + assertEquals(1, originalTools.size(), "Original map should still have 1 entry"); + assertTrue(originalTools.containsKey(originalToolId), "Original map should still contain the original tool"); + } + + @Test + @Tag("integration") + void activeStreamingToolsIsEmptyOnFreshContextCreatedWithCreateMethod() { + // Arrange + Content userContent = mock(Content.class); + InvocationContext ctx = InvocationContext.create(mockSessionService, mockArtifactService, "invocation-id-1", + mockAgent, mockSession, userContent, mockRunConfig); + // Act + Map result = ctx.activeStreamingTools(); + // Assert + assertNotNull(result, "activeStreamingTools map should not be null"); + assertTrue(result.isEmpty(), "activeStreamingTools map should be empty for freshly created context"); + } + + @Test + @Tag("boundary") + void activeStreamingToolsCanHandleNullValueForKey() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + String toolId = "null-value-tool"; + // Act + Map map = invocationContext.activeStreamingTools(); + // Assert - ConcurrentHashMap does not allow null values, verify behavior + assertNotNull(map, "Map should not be null"); + assertThrows(NullPointerException.class, () -> map.put(toolId, null), + "ConcurrentHashMap should throw NullPointerException for null values"); + } + + @Test + @Tag("boundary") + void activeStreamingToolsCanHandleEmptyStringKey() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + String emptyKey = ""; + // Act + invocationContext.activeStreamingTools().put(emptyKey, mockActiveStreamingTool); + Map result = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(result, "Map should not be null"); + assertEquals(1, result.size(), "Map should have 1 entry with empty string key"); + assertTrue(result.containsKey(emptyKey), "Map should contain the empty string key"); + assertEquals(mockActiveStreamingTool, result.get(emptyKey), + "Map should return the correct tool for the empty string key"); + } + + @Test + @Tag("boundary") + void activeStreamingToolsCanHandleVeryLongKeyString() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + String longKey = "a".repeat(10000); + // Act + invocationContext.activeStreamingTools().put(longKey, mockActiveStreamingTool); + Map result = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(result, "Map should not be null"); + assertEquals(1, result.size(), "Map should have 1 entry with long key"); + assertTrue(result.containsKey(longKey), "Map should contain the long key"); + assertEquals(mockActiveStreamingTool, result.get(longKey), + "Map should return the correct tool for the long key"); + } + + @Test + @Tag("boundary") + void activeStreamingToolsClearingMapResultsInEmptyState() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + invocationContext.activeStreamingTools().put("tool-a", mock(ActiveStreamingTool.class)); + invocationContext.activeStreamingTools().put("tool-b", mock(ActiveStreamingTool.class)); + invocationContext.activeStreamingTools().put("tool-c", mock(ActiveStreamingTool.class)); + assertEquals(3, invocationContext.activeStreamingTools().size(), "Map should have 3 entries before clear"); + // Act + invocationContext.activeStreamingTools().clear(); + Map result = invocationContext.activeStreamingTools(); + // Assert + assertNotNull(result, "Map should not be null after clear"); + assertTrue(result.isEmpty(), "Map should be empty after clear"); + assertEquals(0, result.size(), "Map size should be 0 after clear"); + } + + @Test + @Tag("valid") + void activeStreamingToolsReturnedMapContainsSameReferenceAfterUpdate() { + // Arrange + InvocationContext invocationContext = buildMinimalInvocationContext(); + Map mapBeforeUpdate = invocationContext.activeStreamingTools(); + // Act + invocationContext.activeStreamingTools().put("tool-new", mockActiveStreamingTool); + Map mapAfterUpdate = invocationContext.activeStreamingTools(); + // Assert + assertSame(mapBeforeUpdate, mapAfterUpdate, + "The map reference should be the same before and after update"); + assertTrue(mapAfterUpdate.containsKey("tool-new"), + "Updated map should contain the newly added key"); + } + +@Test + @Tag("valid") + void activeStreamingToolsWithBuilderIncludingAllOptionalFields() { + // Arrange + ResumabilityConfig resumabilityConfig = mock(ResumabilityConfig.class); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .agent(mockAgent) + .session(mockSession) + .invocationId("full-builder-invocation-id") + .runConfig(mockRunConfig) + .branch("agentA.agentB") + .resumabilityConfig(resumabilityConfig) + .build(); + // Act \ 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 100644 index 000000000..bcc866a18 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextAgentTest.java @@ -0,0 +1,416 @@ +/* + * 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 Set via Builder + +Details: + TestName: agentReturnsAgentSetViaBuilder + Description: Verifies that the `agent()` method returns the exact `BaseAgent` instance that was set + during the `InvocationContext` construction using the builder pattern. + +Execution: + Arrange: Create a mock or concrete `BaseAgent` instance. Build an `InvocationContext` using + `InvocationContext.builder()` and call `.agent(mockAgent)` along with other required + fields (sessionService, artifactService, session). Call `.build()`. + Act: Call `invocationContext.agent()` on the built context. + Assert: Use `assertEquals` or `assertSame` to verify that the returned agent is the same + instance as the one passed to the builder. + +Validation: + The assertion verifies that the `agent()` getter correctly returns the `BaseAgent` reference + stored internally. This is fundamental to ensuring the invocation context correctly tracks + which agent is being executed during a given 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 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 BaseSessionService mockSessionService; + + @Mock + private BaseArtifactService mockArtifactService; + + @Mock + private BaseMemoryService mockMemoryService; + + @Mock + private PluginManager mockPluginManager; + + @Mock + private BaseAgent mockAgent; + + @Mock + private BaseAgent mockAgent2; + + @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("testSession"); + when(mockAgent.name()).thenReturn("testAgent"); + } + + // ----------------------------------------------------------------------- + // Scenario 1: Return Agent Set via Builder + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void agentReturnsAgentSetViaBuilder() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-001") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseAgent result = context.agent(); + assertNotNull(result, "agent() should not return null when set via builder"); + assertSame(mockAgent, result, "agent() should return the exact same BaseAgent instance set via builder"); + } + + // ----------------------------------------------------------------------- + // Additional valid scenarios + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void agentReturnsSameInstanceAfterSetViaStaticCreateMethod() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, "inv-002", + mockAgent, mockSession, null, mockRunConfig); + BaseAgent result = context.agent(); + assertNotNull(result, "agent() should not return null when set via create()"); + assertSame(mockAgent, result, "agent() should return the same BaseAgent set via create()"); + } + + @Test + @Tag("valid") + void agentReturnsSameReferenceNotACopy() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-003") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseAgent firstCall = context.agent(); + BaseAgent secondCall = context.agent(); + assertSame(firstCall, secondCall, "agent() should return the same reference on repeated calls"); + assertSame(mockAgent, firstCall, "agent() should return the exact instance that was set"); + } + + @Test + @Tag("valid") + void agentReturnsUpdatedAgentAfterMutation() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-004") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(mockAgent, context.agent(), "initial agent should be mockAgent"); + context.agent(mockAgent2); + assertSame(mockAgent2, context.agent(), "agent() should return the updated agent after mutation"); + assertNotSame(mockAgent, context.agent(), "agent() should no longer return the old agent"); + } + + @Test + @Tag("valid") + void agentReturnedFromCopyOfMatchesOriginal() { + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-005") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + assertSame(original.agent(), copy.agent(), + "copyOf() should produce a context whose agent() returns the same instance"); + } + + @Test + @Tag("valid") + void agentIsIndependentBetweenOriginalAndCopyAfterMutation() { + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-006") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + // Mutate the copy's agent + copy.agent(mockAgent2); + assertSame(mockAgent, original.agent(), "Mutating copy's agent should not affect original's agent"); + assertSame(mockAgent2, copy.agent(), "copy.agent() should reflect the mutated agent"); + } + + @Test + @Tag("valid") + void agentBuilderMethodSetsAgentCorrectly() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-007") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + assertEquals(mockAgent.name(), context.agent().name(), "agent name should match the agent set in the builder"); + } + + // ----------------------------------------------------------------------- + // Boundary scenarios + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void agentReturnsNullWhenNotSetInBuilder() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-008") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // agent not set — should return null per field default + assertNull(context.agent(), "agent() should return null when no agent was set in the builder"); + } + + @Test + @Tag("boundary") + void agentCanBeSetToNullViaSetterAfterConstruction() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-009") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(mockAgent, context.agent(), "initially agent should be mockAgent"); + context.agent(null); + assertNull(context.agent(), "agent() should return null after setting agent to null via setter"); + } + + @Test + @Tag("boundary") + void agentSetToSameInstanceMultipleTimesViaBuilder() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-010") + .agent(mockAgent) + .agent(mockAgent) // set twice intentionally + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(mockAgent, context.agent(), + "agent() should correctly return the agent even if set multiple times with the same instance"); + } + + @Test + @Tag("boundary") + void agentSetAndResetSequentiallyViaSetterReturnsLast() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-011") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + context.agent(mockAgent2); + context.agent(mockAgent); + context.agent(mockAgent2); + assertSame(mockAgent2, context.agent(), "agent() should return the last agent set via the setter"); + } + + // ----------------------------------------------------------------------- + // Integration scenarios + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void agentIsRetainedAfterMultipleContextOperations() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId("inv-012") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Simulate setting branch and endInvocation (typical lifecycle operations) + context.branch("parentAgent.testAgent"); + context.setEndInvocation(false); + assertSame(mockAgent, context.agent(), + "agent() should remain unchanged after branch and endInvocation updates"); + } + + @Test + @Tag("integration") + void agentConsistentAcrossAppNameAndUserIdCalls() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-013") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Accessing session-derived properties should not affect agent + String appName = context.appName(); + String userId = context.userId(); + assertNotNull(appName, "appName should not be null"); + assertNotNull(userId, "userId should not be null"); + assertSame(mockAgent, context.agent(), "agent() should remain consistent after accessing appName and userId"); + } + + @Test + @Tag("integration") + void agentFromStaticCreateWithLiveRequestQueueIsReturned() { + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, liveRequestQueue, mockRunConfig); + assertSame(mockAgent, context.agent(), "agent() should return the agent set via live-mode create() factory"); + } + + @Test + @Tag("integration") + void agentNotNullAfterCopyAndBranchMutation() { + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-015") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + copy.branch("newBranch"); + assertNotNull(copy.agent(), "agent() should not be null after copy and branch mutation"); + assertSame(mockAgent, copy.agent(), "agent() on copy should still be the original agent after branch change"); + } + + @Test + @Tag("integration") + void agentEqualityReflectedInContextEquals() { + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-016") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-016") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Both contexts share the same agent mock + assertSame(context1.agent(), context2.agent(), "Both contexts should share the same agent reference"); + assertEquals(context1, context2, "Contexts with equal fields including agent should be equal"); + } + + @Test + @Tag("integration") + void agentDifferenceBreaksContextEquality() { + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-017") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-017") + .agent(mockAgent2) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertNotSame(context1.agent(), context2.agent(), "Different agent instances should not be the same reference"); + assertNotEquals(context1, context2, "Contexts with different agents should not be equal"); + } + +} 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..6d7da3a3e --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextAppNameTest.java @@ -0,0 +1,538 @@ +/* + * 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 When App Name Is a Standard Non-Empty String + +Details: + TestName: appNameReturnsSessionAppName + Description: Verifies that the appName() method correctly delegates to the session's appName() method and returns the exact string value provided during session setup. This is the primary happy-path scenario confirming the delegation chain works correctly. + +Execution: + Arrange: + - Create a mock of Session. + - Configure the mock Session to return a known non-empty string (e.g., "myApplication") when appName() is called. + - Build an InvocationContext using InvocationContext.builder(), setting the mocked session via .session(mockSession). + - Supply any other minimally required builder fields (e.g., sessionService, artifactService, agent) as mocks or stubs. + Act: + - Call invocationContext.appName() on the constructed InvocationContext instance. + Assert: + - Use assertEquals("myApplication", result) to confirm the returned value matches the value configured on the mock session. + +Validation: + This assertion confirms that InvocationContext.appName() faithfully delegates to session.appName() and returns whatever string the session reports as the application name. This is critical because the InvocationContext is the primary API surface used by agents and flows; any discrepancy between the stored session's app name and what InvocationContext reports would cause misrouting of requests. + + +--- + +Scenario 2: Return Application Name Containing Special Characters + +Details: + TestName: appNameReturnsSessionAppNameWithSpecialCharacters + Description: Verifies that the appName() method correctly returns an application name that contains special characters, spaces, hyphens, underscores, and other non-alphanumeric characters without any transformation or truncation. + +Execution: + Arrange: + - Create a mock of Session. + - Configure the mock Session to return a string with special characters (e.g., "my-app_v2.0 (beta)") when appName() is called. + - Build an InvocationContext using InvocationContext.builder() with the mocked session and other required mocked dependencies. + Act: + - Call invocationContext.appName() on the built InvocationContext instance. + Assert: + - Use assertEquals("my-app_v2.0 (beta)", result) to confirm the full special-character string is returned unchanged. + +Validation: + This scenario ensures that no string manipulation, trimming, or encoding is applied inside appName(). Since application names in enterprise environments can include version tags, environment markers, and other special characters, it is important that the method acts as a pure pass-through to the underlying session. + + +--- + +Scenario 3: Return Application Name When It Is a Single Character + +Details: + TestName: appNameReturnsSingleCharacterAppName + Description: Verifies that appName() correctly handles and returns a minimally valid application name consisting of exactly one character, confirming there is no minimum-length enforcement inside the method. + +Execution: + Arrange: + - Create a mock of Session. + - Configure the mock Session to return a single-character string (e.g., "A") when appName() is called. + - Build an InvocationContext using InvocationContext.builder() with the mocked session and other required mocked dependencies. + Act: + - Call invocationContext.appName() on the built InvocationContext instance. + Assert: + - Use assertEquals("A", result) to verify the single character is returned without modification. + +Validation: + This test guards against any accidental length validation or padding logic inside appName(). Even if a single-character name is unusual, the method must reflect whatever the session reports faithfully. + + +--- + +Scenario 4: Return Application Name When It Is a Very Long String + +Details: + TestName: appNameReturnsLongAppName + Description: Verifies that appName() returns a very long application name string without truncation or any form of capping, confirming that no length limit is enforced within the method itself. + +Execution: + Arrange: + - Create a mock of Session. + - Generate a long string of, for example, 1000 characters (e.g., "a".repeat(1000)). + - Configure the mock Session to return this long string when appName() is called. + - Build an InvocationContext using InvocationContext.builder() with the mocked session. + Act: + - Call invocationContext.appName() on the built InvocationContext instance. + Assert: + - Use assertEquals("a".repeat(1000), result) to confirm the full length is preserved. + +Validation: + This test ensures that the appName() method contains no implicit string truncation. If a consuming system relies on the full application name for routing or authorization, even a single character truncation could introduce subtle bugs. + + +--- + +Scenario 5: Return Application Name Consistently on Multiple Invocations + +Details: + TestName: appNameReturnsSameValueOnMultipleCalls + Description: Verifies that calling appName() multiple times on the same InvocationContext instance returns the same consistent value each time, confirming there is no stateful mutation or random behavior inside the method. + +Execution: + Arrange: + - Create a mock of Session. + - Configure the mock Session to return "consistentApp" when appName() is called. + - Build an InvocationContext using InvocationContext.builder() with the mocked session. + Act: + - Call invocationContext.appName() three separate times, storing each result. + Assert: + - Use assertEquals on all three results against "consistentApp" to confirm consistency. + - Optionally use assertEquals(firstResult, secondResult) and assertEquals(secondResult, thirdResult). + +Validation: + Idempotency of the appName() method is essential for thread safety and predictability. Since InvocationContext is used across multiple processing steps in a single invocation, all components must observe the same application name throughout the lifecycle. + + +--- + +Scenario 6: Return Application Name When InvocationContext Is a Copy Created via copyOf + +Details: + TestName: appNameReturnedFromCopiedContext + Description: Verifies that when an InvocationContext is duplicated using the static copyOf() method, the resulting copy also returns the same application name as the original, because the session reference is shared in the shallow copy. + +Execution: + Arrange: + - Create a mock of Session. + - Configure the mock Session to return "originalApp" when appName() is called. + - Build an original InvocationContext using InvocationContext.builder() with the mocked session and all required dependencies. + - Call InvocationContext.copyOf(originalContext) to obtain a copy. + Act: + - Call appName() on both the original and the copied InvocationContext instances. + Assert: + - Use assertEquals("originalApp", originalResult) and assertEquals("originalApp", copiedResult) to confirm both contexts report the same application name. + +Validation: + The copyOf() method performs a shallow copy, meaning the session reference is shared. This test confirms that the application name is accessible through both the original and any derived contexts, which is important when sub-agents or delegated flows create child contexts from a parent context. + + +--- + +Scenario 7: Return Application Name with a Numeric-Only String + +Details: + TestName: appNameReturnsNumericOnlyString + Description: Verifies that appName() correctly returns an application name that consists entirely of numeric characters, confirming that no type conversion or parsing is applied to the session's returned string. + +Execution: + Arrange: + - Create a mock of Session. + - Configure the mock Session to return a numeric string (e.g., "123456") when appName() is called. + - Build an InvocationContext using InvocationContext.builder() with the mocked session and required dependencies. + Act: + - Call invocationContext.appName() on the built InvocationContext instance. + Assert: + - Use assertEquals("123456", result) to verify the numeric string is returned as-is. + +Validation: + Some application naming conventions use numeric identifiers. This test ensures the appName() method treats the return value as a plain string without any implicit numeric conversion, which could result in loss of leading zeros or unexpected formatting. + + +--- + +Scenario 8: Return Application Name When Session Is Provided via Deprecated Constructor + +Details: + TestName: appNameReturnedWhenContextBuiltWithDeprecatedConstructor + Description: Verifies that when an InvocationContext is constructed using one of the deprecated constructors, the appName() method still correctly returns the application name from the session, confirming backward compatibility. + +Execution: + Arrange: + - Create mocks for BaseSessionService, BaseArtifactService, BaseMemoryService, PluginManager, BaseAgent, and Session. + - Configure the mock Session to return "legacyApp" when appName() is called. + - Construct an InvocationContext using the deprecated constructor that accepts BaseSessionService, BaseArtifactService, BaseMemoryService, PluginManager, Optional, Optional branch, String invocationId, BaseAgent, Session, Optional, RunConfig, and boolean endInvocation. + Act: + - Call invocationContext.appName() on the constructed InvocationContext instance. + Assert: + - Use assertEquals("legacyApp", result) to confirm the application name is correctly returned. + +Validation: + Even though the deprecated constructors are marked for removal, they are still in active use in existing codebases. This test ensures that migration paths and legacy integrations continue to function correctly and that appName() behaves identically regardless of how the InvocationContext was instantiated. + + +--- + +Scenario 9: Verify appName() Calls Session's appName() Exactly Once Per Invocation + +Details: + TestName: appNameDelegatesExactlyOnceToSession + Description: Verifies that for a single call to InvocationContext.appName(), the underlying session.appName() method is called exactly once, confirming there is no caching layer, no extra calls, and no skipping of the delegation. + +Execution: + Arrange: + - Create a mock of Session using a mocking framework such as Mockito. + - Configure the mock Session to return "delegationApp" when appName() is called. + - Build an InvocationContext using InvocationContext.builder() with the mocked session and other required dependencies. + Act: + - Call invocationContext.appName() exactly once. + Assert: + - Use Mockito.verify(mockSession, times(1)).appName() to confirm session.appName() was called exactly once. + - Additionally use assertEquals("delegationApp", result) to confirm the correct value was returned. + +Validation: + This test validates the delegation contract between InvocationContext and Session. If multiple calls were made internally, it could indicate unintended behavior such as logging, auditing side effects, or retry logic. Exactly one delegation call confirms the method is a pure, lightweight wrapper. + + +--- + +Scenario 10: Return Application Name When InvocationContext Is Built Using the Static create() Method + +Details: + TestName: appNameReturnedWhenContextCreatedViaStaticCreateMethod + Description: Verifies that when an InvocationContext is created using the deprecated static create() factory method that accepts sessionService, artifactService, invocationId, agent, session, userContent, and runConfig, the appName() method still correctly returns the application name from the session. + +Execution: + Arrange: + - Create mocks for BaseSessionService, BaseArtifactService, BaseAgent, and Session. + - Configure the mock Session to return "staticFactoryApp" when appName() is called. + - Call InvocationContext.create(mockSessionService, mockArtifactService, "inv-001", mockAgent, mockSession, null, RunConfig.builder().build()) to obtain an InvocationContext instance. + Act: + - Call invocationContext.appName() on the resulting InvocationContext instance. + Assert: + - Use assertEquals("staticFactoryApp", result) to verify the returned value matches the session's app name. + +Validation: + The static create() factory methods were the primary construction mechanism before the builder pattern was introduced. Verifying that appName() works correctly with these factory-created instances ensures that any legacy calling code continues to receive accurate application name information without requiring migration to the builder pattern. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.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(); + } + + private InvocationContext buildDefaultContext(Session session) { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .build(); + } + + @Test + @Tag("valid") + void appNameReturnsSessionAppName() { + when(mockSession.appName()).thenReturn("myApplication"); + InvocationContext invocationContext = buildDefaultContext(mockSession); + String result = invocationContext.appName(); + assertEquals("myApplication", result); + } + + @Test + @Tag("valid") + void appNameReturnsSessionAppNameWithSpecialCharacters() { + when(mockSession.appName()).thenReturn("my-app_v2.0 (beta)"); + InvocationContext invocationContext = buildDefaultContext(mockSession); + String result = invocationContext.appName(); + assertEquals("my-app_v2.0 (beta)", result); + } + + @Test + @Tag("boundary") + void appNameReturnsSingleCharacterAppName() { + when(mockSession.appName()).thenReturn("A"); + InvocationContext invocationContext = buildDefaultContext(mockSession); + String result = invocationContext.appName(); + assertEquals("A", result); + } + + @Test + @Tag("boundary") + void appNameReturnsLongAppName() { + String longAppName = "a".repeat(1000); + when(mockSession.appName()).thenReturn(longAppName); + InvocationContext invocationContext = buildDefaultContext(mockSession); + String result = invocationContext.appName(); + assertEquals(longAppName, result); + assertEquals(1000, result.length()); + } + + @Test + @Tag("valid") + void appNameReturnsSameValueOnMultipleCalls() { + when(mockSession.appName()).thenReturn("consistentApp"); + InvocationContext invocationContext = buildDefaultContext(mockSession); + String firstResult = invocationContext.appName(); + String secondResult = invocationContext.appName(); + String thirdResult = invocationContext.appName(); + assertEquals("consistentApp", firstResult); + assertEquals("consistentApp", secondResult); + assertEquals("consistentApp", thirdResult); + assertEquals(firstResult, secondResult); + assertEquals(secondResult, thirdResult); + } + + @Test + @Tag("integration") + void appNameReturnedFromCopiedContext() { + when(mockSession.appName()).thenReturn("originalApp"); + InvocationContext originalContext = buildDefaultContext(mockSession); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + String originalResult = originalContext.appName(); + String copiedResult = copiedContext.appName(); + assertEquals("originalApp", originalResult); + assertEquals("originalApp", copiedResult); + assertEquals(originalResult, copiedResult); + } + + @Test + @Tag("valid") + void appNameReturnsNumericOnlyString() { + when(mockSession.appName()).thenReturn("123456"); + InvocationContext invocationContext = buildDefaultContext(mockSession); + String result = invocationContext.appName(); + assertEquals("123456", result); + } + + @Test + @Tag("valid") + @SuppressWarnings("deprecation") + void appNameReturnedWhenContextBuiltWithDeprecatedConstructor() { + when(mockSession.appName()).thenReturn("legacyApp"); + InvocationContext invocationContext = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + Optional.empty(), + Optional.empty(), + "legacy-invocation-id", + mockAgent, + mockSession, + Optional.empty(), + defaultRunConfig, + false); + String result = invocationContext.appName(); + assertEquals("legacyApp", result); + } + + @Test + @Tag("valid") + void appNameDelegatesExactlyOnceToSession() { + when(mockSession.appName()).thenReturn("delegationApp"); + InvocationContext invocationContext = buildDefaultContext(mockSession); + String result = invocationContext.appName(); + assertEquals("delegationApp", result); + verify(mockSession, times(1)).appName(); + } + + @Test + @Tag("integration") + void appNameReturnedWhenContextCreatedViaStaticCreateMethod() { + when(mockSession.appName()).thenReturn("staticFactoryApp"); + InvocationContext invocationContext = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "inv-001", + mockAgent, + mockSession, + null, + defaultRunConfig); + String result = invocationContext.appName(); + assertEquals("staticFactoryApp", result); + } + + @Test + @Tag("valid") + void appNameReturnedFromBuilderWithAllFieldsSet() { + when(mockSession.appName()).thenReturn("fullBuilderApp"); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .liveRequestQueue(Optional.empty()) + .branch(Optional.empty()) + .invocationId("full-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .endInvocation(false) + .build(); + String result = invocationContext.appName(); + assertEquals("fullBuilderApp", result); + } + + @Test + @Tag("valid") + void appNameReturnedFromContextWithBranchSet() { + when(mockSession.appName()).thenReturn("branchedApp"); + InvocationContext invocationContext = + InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("branched-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(defaultRunConfig) + .branch("parentAgent.childAgent") + .build(); + String result = invocationContext.appName(); + assertEquals("branchedApp", result); + } + + @Test + @Tag("valid") + void appNameReturnedWhenContextCreatedViaSecondStaticCreateMethod() { + when(mockSession.appName()).thenReturn("liveContextApp"); + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext invocationContext = + InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + liveRequestQueue, + defaultRunConfig); + String result = invocationContext.appName(); + assertEquals("liveContextApp", result); + } + + @Test + @Tag("boundary") + void appNameReturnsEmptyStringWhenSessionReturnsEmptyString() { + when(mockSession.appName()).thenReturn(""); + InvocationContext invocationContext = buildDefaultContext(mockSession); + String result = invocationContext.appName(); + assertEquals("", result); + } + + @Test + @Tag("valid") + void appNameReturnsDifferentValuesForDifferentSessions() { + Session anotherMockSession = mock(Session.class); + when(mockSession.appName()).thenReturn("appOne"); + when(anotherMockSession.appName()).thenReturn("appTwo"); + InvocationContext contextOne = buildDefaultContext(mockSession); + InvocationContext contextTwo = buildDefaultContext(anotherMockSession); + String resultOne = contextOne.appName(); + String resultTwo = contextTwo.appName(); + assertEquals("appOne", resultOne); + assertEquals("appTwo", resultTwo); + } + + @Test + @Tag("valid") + void appNameReturnedWithUnicodeCharacters() { + when(mockSession.appName()).thenReturn("アプリケーション名"); + InvocationContext invocationContext = buildDefaultContext(mockSession); + String result = invocationContext.appName(); + assertEquals("アプリケーション名", result); + } + + @Test + @Tag("valid") + void appNameVerifySessionInteractionOnMultipleCalls() { + when(mockSession.appName()).thenReturn("multiCallApp"); + InvocationContext invocationContext = buildDefaultContext(mockSession); + invocationContext.appName(); + invocationContext.appName(); + invocationContext.appName(); + verify(mockSession, times(3)).appName(); + } + +} 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 100644 index 000000000..44086af2b --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextArtifactServiceTest.java @@ -0,0 +1,638 @@ +/* + * 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: Return Non-Null ArtifactService When Set via Builder + +Details: + TestName: artifactServiceReturnsCorrectInstanceWhenSetViaBuilder + Description: Verifies that the artifactService() method returns the exact same BaseArtifactService instance + that was provided during the construction of InvocationContext using the Builder pattern. + This is the primary happy-path scenario confirming the field is correctly stored and returned. +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() on the constructed InvocationContext instance. + Assert: + - Use assertSame() or assertEquals() to verify that the returned BaseArtifactService + is the exact same instance as the one provided to the builder. +Validation: + The assertion ensures that artifactService() is a simple accessor (getter) that does not + modify, wrap, or substitute the original service instance. This is critical for ensuring + that all artifact operations within an invocation are performed on the correct, intended + service object, maintaining consistency and preventing unexpected behavior. + + +Scenario 2: Return Null ArtifactService When Not Set via Builder + +Details: + TestName: artifactServiceReturnsNullWhenNotSetViaBuilder + Description: Verifies that the artifactService() method returns null when no BaseArtifactService + was provided to the builder. Since the builder field defaults to null and no validation + is enforced at build time (as per the TODO comment in the source), the method should + return null. +Execution: + Arrange: + - 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) + .agent(mockAgent) + .session(mockSession) + .build() + WITHOUT calling .artifactService(...). + Act: + - Call invocationContext.artifactService() on the constructed InvocationContext instance. + Assert: + - Use assertNull() to verify that the returned BaseArtifactService is null. +Validation: + This test confirms that the method faithfully returns whatever value (including null) is + stored in the private final field. It also highlights that the InvocationContext does not + enforce non-null artifact service at construction time, which is important for callers + to be aware of when using the context in environments where artifact persistence is optional. + + +Scenario 3: ArtifactService Instance Is Preserved After CopyOf Operation + +Details: + TestName: artifactServiceIsPreservedInCopiedContext + Description: Verifies that when InvocationContext.copyOf() is used to create a shallow copy + of an InvocationContext, the new context's artifactService() method returns the + same BaseArtifactService instance as the original context. +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 original InvocationContext using InvocationContext.builder() + with the mockArtifactService set. + - Create a copy using InvocationContext.copyOf(originalContext). + Act: + - Call artifactService() on the copied InvocationContext instance. + Assert: + - Use assertSame() to verify that the copied context's artifactService() + returns the same instance as the original context's artifactService(). +Validation: + This test confirms the shallow copy semantics of InvocationContext.copyOf(). Since the copy + shares the same artifact service reference, this ensures that sub-agent invocations or + delegated contexts all operate on the same artifact persistence layer, which is essential + for consistent artifact management throughout a single invocation tree. + + +Scenario 4: ArtifactService Instance Identity Is Maintained (Not Cloned or Wrapped) + +Details: + TestName: artifactServiceReturnsSameReferenceNotACopy + Description: Verifies that calling artifactService() multiple times on the same InvocationContext + instance always returns the identical object reference, confirming the field is + final and the method is a pure accessor with no side effects. +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 with the mockArtifactService via InvocationContext.builder(). + Act: + - Call invocationContext.artifactService() twice, storing the result in two separate variables: + firstCall and secondCall. + Assert: + - Use assertSame() to verify that firstCall and secondCall refer to the exact same object instance. +Validation: + Since artifactService is a private final field, it cannot be reassigned after construction. + This test confirms the idempotency and referential transparency of the accessor method. + This is important in multi-step processing pipelines where the same context is referenced + multiple times — consumers can rely on receiving the same service instance every time. + + +Scenario 5: ArtifactService Returns Correct Instance When Set via Deprecated Constructor + +Details: + TestName: artifactServiceReturnsCorrectInstanceWhenSetViaDeprecatedConstructor + Description: Verifies that the artifactService() method returns the correct BaseArtifactService + instance when the InvocationContext is constructed using the deprecated public + constructor (which delegates to the builder internally). +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 BaseMemoryService. + - Create a mock or concrete implementation of PluginManager. + - Create a mock or concrete implementation of Session. + - Create a mock or concrete implementation of BaseAgent. + - Instantiate InvocationContext using the deprecated constructor: + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + Optional.empty(), // liveRequestQueue + Optional.empty(), // branch + "test-invocation-id", + mockAgent, + mockSession, + Optional.empty(), // userContent + RunConfig.builder().build(), + false // endInvocation + ) + Act: + - Call invocationContext.artifactService() on the constructed InvocationContext. + Assert: + - Use assertSame() to verify the returned instance is the same mockArtifactService + that was passed to the constructor. +Validation: + This scenario ensures backward compatibility of the deprecated constructor path. + Even though the deprecated constructor is marked for removal, it currently delegates + to the builder, so the artifact service should be correctly wired. This confirms + the delegation chain is intact and the field assignment in the private constructor + properly stores the value. + + +Scenario 6: ArtifactService Is Correctly Included in Equality Check + +Details: + TestName: artifactServiceDifferenceBreaksContextEquality + Description: Verifies that two InvocationContext instances built with different + BaseArtifactService instances are not considered equal, confirming + that the artifactService field participates in the equals() contract. +Execution: + Arrange: + - Create two distinct mock instances of BaseArtifactService: mockArtifactService1 + and mockArtifactService2. + - Create shared mocks for BaseSessionService, Session, and BaseAgent. + - Build contextA using InvocationContext.builder() + with mockArtifactService1 and all other shared fields. + - Build contextB using InvocationContext.builder() + with mockArtifactService2 and identical values for all other fields. + Act: + - Call contextA.artifactService() and contextB.artifactService(). + - Also call contextA.equals(contextB). + Assert: + - Use assertNotSame() to confirm that contextA.artifactService() and contextB.artifactService() + are different instances. + - Use assertFalse() to confirm that contextA.equals(contextB) returns false. +Validation: + This test confirms that artifactService is part of the equals() contract as defined in + the InvocationContext.equals() method. This is important for scenarios where contexts + are compared (e.g., in collections or deduplication logic), ensuring that two contexts + with different artifact services are correctly distinguished as unequal. + + +Scenario 7: ArtifactService Is Correctly Included in HashCode Computation + +Details: + TestName: artifactServiceParticipatesInHashCode + Description: Verifies that two InvocationContext instances with different BaseArtifactService + instances produce different hash codes, confirming that artifactService participates + in the hashCode() computation as declared in InvocationContext.hashCode(). +Execution: + Arrange: + - Create two distinct mock instances of BaseArtifactService: mockArtifactService1 + and mockArtifactService2. + - Create shared mocks for BaseSessionService, Session, and BaseAgent. + - Build contextA with mockArtifactService1 using InvocationContext.builder(). + - Build contextB with mockArtifactService2 using InvocationContext.builder() + with identical values for all other fields. + Act: + - Call contextA.hashCode() and contextB.hashCode(), storing each result. + Assert: + - Use assertNotEquals() to verify that the two hash codes are different + (acknowledging this is probabilistic but highly likely for distinct service objects). +Validation: + This test validates that the artifactService field is included in the hash code calculation + as explicitly listed in the InvocationContext.hashCode() method. Correct hash code + behavior is critical for proper functioning of hash-based collections (HashMap, HashSet) + that may contain InvocationContext instances. + + +Scenario 8: ArtifactService Is Identical Between Original and Copied Context + +Details: + TestName: artifactServiceIsSameReferenceAfterCopyOf + Description: Verifies that after creating a copy via InvocationContext.copyOf(), the + artifactService() returned from both the original and copied context point + to the exact same instance in memory, confirming shallow copy semantics + for this field. +Execution: + Arrange: + - Create a mock of BaseArtifactService. + - Build an original InvocationContext with the mock artifact service set. + - Create a copy via InvocationContext.copyOf(originalContext). + Act: + - Call originalContext.artifactService() and copiedContext.artifactService(). + Assert: + - Use assertSame() to verify that both calls return the identical object reference. + - Use assertEquals() to further confirm equality of the returned values. +Validation: + Shallow copy means object references are shared, not deep-copied. This test confirms + that artifact service sharing between a parent context and its copy is intentional and + consistent. This is significant because it means operations performed on artifacts + within either context affect the same underlying service, which is the expected + behavior for sub-agent delegations within the same invocation. + + +Scenario 9: ArtifactService Returns Correct Type (BaseArtifactService) + +Details: + TestName: artifactServiceReturnsBaseArtifactServiceType + Description: Verifies that the return type of artifactService() is BaseArtifactService + and that the returned object can be used as a BaseArtifactService reference, + even when a concrete subclass implementation is provided. +Execution: + Arrange: + - Create a concrete subclass or mock implementation of BaseArtifactService + (e.g., a Mockito mock of a hypothetical concrete implementation). + - Build an InvocationContext via builder() with the concrete artifact service instance. + Act: + - Call invocationContext.artifactService(), assigning the result to a variable + declared as BaseArtifactService. + Assert: + - Use assertNotNull() to confirm the returned value is not null. + - Use assertInstanceOf(BaseArtifactService.class, ...) to confirm the returned + object is an instance of BaseArtifactService. +Validation: + This test ensures that the method's return type contract is honored — it returns + a BaseArtifactService reference regardless of the concrete implementation provided. + This is important for polymorphic usage patterns where callers interact only + with the abstract BaseArtifactService interface, enabling flexible service substitution. + +*/ + +// ********RoostGPT******** + +package com.google.adk.agents; + +import static org.junit.jupiter.api.Assertions.*; + +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 InvocationContextArtifactServiceTest { + + @Mock + private BaseArtifactService mockArtifactService; + + @Mock + private BaseArtifactService mockArtifactService1; + + @Mock + private BaseArtifactService mockArtifactService2; + + @Mock + private BaseSessionService mockSessionService; + + @Mock + private BaseMemoryService mockMemoryService; + + @Mock + private PluginManager mockPluginManager; + + @Mock + private Session mockSession; + + @Mock + private BaseAgent mockAgent; + + @Mock + private RunConfig mockRunConfig; + + private static final String TEST_INVOCATION_ID = "test-invocation-id"; + + @BeforeEach + void setUp() { + // Common setup if needed; individual tests handle their own arrangements. + } + + @Test + @Tag("valid") + void artifactServiceReturnsCorrectInstanceWhenSetViaBuilder() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + BaseArtifactService result = invocationContext.artifactService(); + // Assert + assertSame(mockArtifactService, result, + "artifactService() should return the exact same instance provided to the builder"); + } + + @Test + @Tag("boundary") + void artifactServiceReturnsNullWhenNotSetViaBuilder() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + BaseArtifactService result = invocationContext.artifactService(); + // Assert + assertNull(result, "artifactService() should return null when no artifact service was provided to the builder"); + } + + @Test + @Tag("integration") + void artifactServiceIsPreservedInCopiedContext() { + // Arrange + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + // Act + BaseArtifactService resultFromCopy = copiedContext.artifactService(); + // Assert + assertSame(mockArtifactService, resultFromCopy, + "Copied context's artifactService() should return the same instance as the original"); + } + + @Test + @Tag("valid") + void artifactServiceReturnsSameReferenceNotACopy() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + BaseArtifactService firstCall = invocationContext.artifactService(); + BaseArtifactService secondCall = invocationContext.artifactService(); + // Assert + assertSame(firstCall, secondCall, + "Multiple calls to artifactService() should return the exact same object reference"); + } + + @Test + @Tag("valid") + @SuppressWarnings("deprecation") + void artifactServiceReturnsCorrectInstanceWhenSetViaDeprecatedConstructor() { + // Arrange + InvocationContext invocationContext = new InvocationContext(mockSessionService, mockArtifactService, + mockMemoryService, mockPluginManager, Optional.empty(), Optional.empty(), TEST_INVOCATION_ID, mockAgent, + mockSession, Optional.empty(), RunConfig.builder().build(), false); + // Act + BaseArtifactService result = invocationContext.artifactService(); + // Assert + assertSame(mockArtifactService, result, + "artifactService() should return the same instance passed via the deprecated constructor"); + } + + @Test + @Tag("valid") + void artifactServiceDifferenceBreaksContextEquality() { + // Arrange + InvocationContext contextA = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService1) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + InvocationContext contextB = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService2) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + BaseArtifactService serviceA = contextA.artifactService(); + BaseArtifactService serviceB = contextB.artifactService(); + boolean areEqual = contextA.equals(contextB); + // Assert + assertNotSame(serviceA, serviceB, + "Two contexts with different artifact services should have different service instances"); + assertFalse(areEqual, "Two InvocationContext instances with different artifact services should not be equal"); + } + + @Test + @Tag("valid") + void artifactServiceParticipatesInHashCode() { + // Arrange + InvocationContext contextA = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService1) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + InvocationContext contextB = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService2) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + int hashCodeA = contextA.hashCode(); + int hashCodeB = contextB.hashCode(); + // Assert + assertNotEquals(hashCodeA, hashCodeB, + "Two contexts with different artifact services should typically produce different hash codes"); + } + + @Test + @Tag("integration") + void artifactServiceIsSameReferenceAfterCopyOf() { + // Arrange + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + // Act + BaseArtifactService originalService = originalContext.artifactService(); + BaseArtifactService copiedService = copiedContext.artifactService(); + // Assert + assertSame(originalService, copiedService, + "Both original and copied context should return the exact same artifact service reference"); + assertEquals(originalService, copiedService, + "Both original and copied context's artifact services should be equal"); + } + + @Test + @Tag("valid") + void artifactServiceReturnsBaseArtifactServiceType() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + BaseArtifactService result = invocationContext.artifactService(); + // Assert + assertNotNull(result, "Returned artifact service should not be null"); + assertInstanceOf(BaseArtifactService.class, result, + "Returned value should be an instance of BaseArtifactService"); + } + + @Test + @Tag("valid") + void artifactServiceReturnedByBuilderMatchesOriginalInvocationContextCreator() { + // Arrange + InvocationContext invocationContext = InvocationContext.create(mockSessionService, mockArtifactService, + TEST_INVOCATION_ID, mockAgent, mockSession, null, RunConfig.builder().build()); + // Act + BaseArtifactService result = invocationContext.artifactService(); + // Assert + assertSame(mockArtifactService, result, + "artifactService() should return the same instance passed to InvocationContext.create()"); + } + + @Test + @Tag("boundary") + void artifactServiceIsNullWhenBuiltWithMinimalParametersForLiveContext() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + BaseArtifactService result = invocationContext.artifactService(); + // Assert + assertNull(result, "artifactService() should be null when artifact service is not explicitly set on builder"); + } + + @Test + @Tag("valid") + void artifactServiceAccessorIsIdempotentAcrossMultipleInvocations() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .build(); + // Act + BaseArtifactService call1 = invocationContext.artifactService(); + BaseArtifactService call2 = invocationContext.artifactService(); + BaseArtifactService call3 = invocationContext.artifactService(); + // Assert + assertSame(call1, call2, "First and second call should return the same reference"); + assertSame(call2, call3, "Second and third call should return the same reference"); + assertSame(call1, call3, "First and third call should return the same reference"); + } + + @Test + @Tag("integration") + @SuppressWarnings("deprecation") + void artifactServiceIsConsistentBetweenDeprecatedConstructorAndBuilderApproach() { + // Arrange + InvocationContext contextViaBuilder = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .liveRequestQueue(Optional.empty()) + .branch(Optional.empty()) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(RunConfig.builder().build()) + .endInvocation(false) + .build(); + InvocationContext contextViaDeprecatedConstructor = new InvocationContext(mockSessionService, + mockArtifactService, mockMemoryService, mockPluginManager, Optional.empty(), Optional.empty(), + TEST_INVOCATION_ID, mockAgent, mockSession, Optional.empty(), RunConfig.builder().build(), false); + // Act + BaseArtifactService serviceFromBuilder = contextViaBuilder.artifactService(); + BaseArtifactService serviceFromDeprecated = contextViaDeprecatedConstructor.artifactService(); + // Assert + assertSame(mockArtifactService, serviceFromBuilder, + "Builder approach should return the correct artifact service"); + assertSame(mockArtifactService, serviceFromDeprecated, + "Deprecated constructor approach should return the correct artifact service"); + assertSame(serviceFromBuilder, serviceFromDeprecated, + "Both approaches should return the same artifact service instance"); + } + +} diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextBranch941Test.java b/core/src/test/java/com/google/adk/agents/InvocationContextBranch941Test.java new file mode 100644 index 000000000..9a6bcd032 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextBranch941Test.java @@ -0,0 +1,461 @@ +/* + * 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: Verify that branch() returns an empty Optional when no branch is set during construction + +Details: + TestName: branchReturnsEmptyOptionalWhenNotSet + Description: Verifies that when an InvocationContext is built without explicitly setting a branch value, + the branch() method returns an empty Optional, confirming the default state is Optional.empty(). + +Execution: + Arrange: Build an InvocationContext using InvocationContext.builder() without calling .branch(...), + providing only the minimum required fields (e.g., sessionService, artifactService, session, agent). + Act: Call branch() on the constructed InvocationContext instance. + Assert: Assert that the returned Optional is empty using assertTrue(context.branch().isEmpty()). + +Validation: + This test ensures that the default value of the branch field is Optional.empty() as initialized + in the Builder. It is important for application behavior because callers depending on branch() + must correctly handle the absent case to avoid NullPointerExceptions or incorrect routing logic. + +*/ + +// ********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 InvocationContextBranch941Test { + + @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"); + when(mockSession.id()).thenReturn("testSessionId"); + } + + @Test + @Tag("valid") + void branchReturnsEmptyOptionalWhenNotSet() { + // 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.branch(); + // Assert + assertTrue(result.isEmpty(), "Branch should be empty when not set during construction"); + } + + @Test + @Tag("valid") + void branchReturnsCorrectValueWhenSetAsString() { + // Arrange + String expectedBranch = "agentA.agentB.agentC"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(expectedBranch) + .build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent(), "Branch should be present when set"); + assertEquals(expectedBranch, result.get(), "Branch value should match what was set"); + } + + @Test + @Tag("valid") + void branchReturnsCorrectValueWhenSetAsOptional() { + // Arrange + String expectedBranch = "agentA.agentB"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(Optional.of(expectedBranch)) + .build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent(), "Branch should be present when set as Optional"); + assertEquals(expectedBranch, result.get(), "Branch value should match what was set"); + } + + @Test + @Tag("valid") + void branchReturnsEmptyOptionalWhenSetAsEmptyOptional() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(Optional.empty()) + .build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isEmpty(), "Branch should be empty when set as Optional.empty()"); + } + + @Test + @Tag("valid") + void branchReturnsUpdatedValueAfterMutation() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + context.branch("newBranch.subBranch"); + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent(), "Branch should be present after mutation"); + assertEquals("newBranch.subBranch", result.get(), "Branch value should match the updated value"); + } + + @Test + @Tag("valid") + void branchReturnsEmptyAfterSettingNullViaSetterMethod() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch("initialBranch") + .build(); + // Act + context.branch((String) null); + Optional result = context.branch(); + // Assert + assertTrue(result.isEmpty(), "Branch should be empty after setting null via setter"); + } + + @Test + @Tag("boundary") + void branchReturnsOptionalWithEmptyStringWhenSetToEmptyString() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch("") + .build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent(), "Branch should be present when set to an empty string"); + assertEquals("", result.get(), "Branch value should be an empty string"); + } + + @Test + @Tag("boundary") + void branchReturnsOptionalWithSingleAgentName() { + // Arrange + String singleAgent = "agentA"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(singleAgent) + .build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent(), "Branch should be present for single agent"); + assertEquals(singleAgent, result.get(), "Branch value should match single agent name"); + } + + @Test + @Tag("boundary") + void branchReturnsOptionalWithLongHierarchicalName() { + // Arrange + String longBranch = "agentA.agentB.agentC.agentD.agentE.agentF"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(longBranch) + .build(); + // Act + Optional result = context.branch(); + // Assert + assertTrue(result.isPresent(), "Branch should be present for long hierarchical name"); + assertEquals(longBranch, result.get(), "Branch value should match the long hierarchical name"); + } + + @Test + @Tag("valid") + void branchPreservedInCopyOf() { + // Arrange + String branchValue = "parentAgent.childAgent"; + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(branchValue) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + Optional result = copy.branch(); + // Assert + assertTrue(result.isPresent(), "Branch should be present in copied context"); + assertEquals(branchValue, result.get(), "Branch value should be preserved in copy"); + } + + @Test + @Tag("valid") + void branchEmptyPreservedInCopyOf() { + // 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 copy = InvocationContext.copyOf(original); + Optional result = copy.branch(); + // Assert + assertTrue(result.isEmpty(), "Branch should remain empty in copied context when not originally set"); + } + + @Test + @Tag("valid") + void branchMutationOnCopyDoesNotAffectOriginal() { + // Arrange + String originalBranch = "originalAgent"; + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(originalBranch) + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + // Act + copy.branch("newBranch.newChild"); + // Assert + Optional originalResult = original.branch(); + Optional copyResult = copy.branch(); + assertTrue(originalResult.isPresent(), "Original branch should still be present"); + assertEquals(originalBranch, originalResult.get(), "Original branch should not change when copy is mutated"); + assertTrue(copyResult.isPresent(), "Copy branch should be present after mutation"); + assertEquals("newBranch.newChild", copyResult.get(), "Copy branch should have new value"); + } + + @Test + @Tag("valid") + void branchReturnTypeIsOptional() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + Object result = context.branch(); + // Assert + assertNotNull(result, "branch() should not return null, it should return Optional.empty()"); + assertInstanceOf(Optional.class, result, "branch() should return an Optional type"); + } + + @Test + @Tag("valid") + void branchUsedInCreateStaticFactory() { + // Arrange + Content userContent = mock(Content.class); + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, + "test-invocation-id", mockAgent, mockSession, userContent, mockRunConfig); + // Act + Optional result = context.branch(); + // Assert + assertNotNull(result, "branch() should not return null when created via static factory"); + assertTrue(result.isEmpty(), "Branch should be empty when created via static factory without branch"); + } + + @Test + @Tag("integration") + void branchConsistentAcrossMultipleCallsOnSameContext() { + // Arrange + String branchValue = "agent1.agent2.agent3"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .branch(branchValue) + .build(); + // Act + Optional result1 = context.branch(); + Optional result2 = context.branch(); + Optional result3 = context.branch(); + // Assert + assertEquals(result1, result2, "branch() should return same value on repeated calls"); + assertEquals(result2, result3, "branch() should return same value on repeated calls"); + assertTrue(result1.isPresent(), "Branch should be consistently present"); + assertEquals(branchValue, result1.get(), "Branch value should be consistently correct"); + } + + @Test + @Tag("integration") + void branchUpdatesReflectedInSubsequentCalls() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act & Assert - Initial state + assertTrue(context.branch().isEmpty(), "Initially branch should be empty"); + // Mutate branch + context.branch("firstBranch"); + assertTrue(context.branch().isPresent(), "Branch should be present after first update"); + assertEquals("firstBranch", context.branch().get(), "Branch should reflect first update"); + // Mutate branch again + context.branch("firstBranch.secondBranch"); + assertTrue(context.branch().isPresent(), "Branch should be present after second update"); + assertEquals("firstBranch.secondBranch", context.branch().get(), "Branch should reflect second update"); + } + +@Test + @Tag("valid") + void branchWithSpecialCharactersInName() { + // Arrange + String specialBranch = "agent-1.agent_2.agent3"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocation \ No newline at end of file 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 100644 index 000000000..13b79e81d --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextBranchTest.java @@ -0,0 +1,498 @@ +/* + * 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: branchIsSetWithValidNonNullString + Description: Verifies that when a valid, non-null string is passed to the branch() method, + the internal branch field is updated to an Optional containing that string value. +Execution: + Arrange: Build an InvocationContext instance using the builder with no initial branch set. + Prepare a valid branch string value such as "feature-branch-1". + Act: Call context.branch("feature-branch-1") on the constructed InvocationContext. + Assert: Call context.branch() and assert that the returned Optional is present and equals "feature-branch-1". +Validation: + The assertion verifies that a valid non-null string is correctly wrapped in an Optional and stored. + This confirms the basic functionality of the branch setter — it must reliably update the field + when a meaningful branch identifier is provided, which is critical for tracking conversation forks. + +--- + +Scenario 2: Setting Branch to Null Clears the Branch Value + +Details: + TestName: branchIsSetToEmptyWhenNullIsPassed + Description: Verifies that when null is explicitly passed to the branch() method, the branch + field is set to Optional.empty(), effectively clearing any previously held branch value. +Execution: + Arrange: Build an InvocationContext with an initial branch value set via the builder (e.g., "old-branch"). + Act: Call context.branch(null) on the InvocationContext instance. + Assert: Call context.branch() and assert that the returned Optional is empty (not present). +Validation: + The method uses Optional.ofNullable(branch), which means a null argument produces Optional.empty(). + This scenario is critical to ensure that callers can explicitly unset a branch, allowing the + invocation context to reflect that no branch is active without causing a NullPointerException. + +--- + +Scenario 3: Setting Branch Overwrites a Previously Set Non-Null Branch + +Details: + TestName: branchOverwritesPreviousNonNullValue + Description: Verifies that calling the branch() method multiple times replaces the previously + stored branch value with the new one. +Execution: + Arrange: Build an InvocationContext with an initial branch of "branch-v1" via the builder. + Act: Call context.branch("branch-v2") on the InvocationContext instance. + Assert: Call context.branch() and assert that the returned Optional contains "branch-v2" and + not "branch-v1". +Validation: + This confirms that the branch setter is idempotent in overwriting — the most recently set value + should always be reflected. This is important when the invocation context is being reused or + updated during a session to reflect a new conversation fork. + +--- + +Scenario 4: Setting Branch with an Empty String + +Details: + TestName: branchIsSetWithEmptyString + Description: Verifies that when an empty string "" is passed to the branch() method, it is + treated as a valid (non-null) value and wrapped in an Optional. +Execution: + Arrange: Build an InvocationContext instance using the builder with default settings. + Act: Call context.branch("") on the InvocationContext instance. + Assert: Call context.branch() and assert that the returned Optional is present and that + the contained value equals an empty string "". +Validation: + Optional.ofNullable("") returns Optional.of(""), not Optional.empty(). This scenario ensures + that an empty string is not conflated with null. Whether an empty string is semantically valid + as a branch ID is a business concern, but the method must handle it correctly at the technical + level. + +--- + +Scenario 5: Setting Branch with a Whitespace-Only String + +Details: + TestName: branchIsSetWithWhitespaceOnlyString + Description: Verifies that a string containing only whitespace characters is accepted by + branch() and stored as-is within the Optional. +Execution: + Arrange: Build an InvocationContext instance using the builder with default settings. + Act: Call context.branch(" ") on the InvocationContext instance. + Assert: Call context.branch() and assert that the returned Optional is present and its value + equals " " (three spaces). +Validation: + The method performs no trimming or validation on the input. This test ensures that the method + does not apply any implicit transformation to the input value. Whitespace strings are technically + non-null and should be stored faithfully. + +--- + +Scenario 6: Setting Branch Does Not Affect Other Fields of InvocationContext + +Details: + TestName: branchSetterDoesNotAffectOtherContextFields + Description: Verifies that calling the branch() setter only modifies the branch field and + leaves all other fields in the InvocationContext unchanged. +Execution: + Arrange: Build an InvocationContext with specific values: set endInvocation to false, + and invocationId to a known value via the builder. Record the invocationId + by calling context.invocationId() before the act. + Act: Call context.branch("new-branch") on the InvocationContext. + Assert: Assert that context.branch() returns Optional.of("new-branch"), + context.invocationId() still returns the same invocationId value recorded earlier, + and context.endInvocation() still returns false. +Validation: + This ensures the branch setter is narrowly scoped and does not introduce side effects on other + context fields. Unintended mutation of shared context state could cause hard-to-diagnose bugs + in multi-agent or sub-agent invocation scenarios. + +--- + +Scenario 7: Setting Branch from Null to a Valid String + +Details: + TestName: branchTransitionsFromNullToValidString + Description: Verifies that after the branch has been set to null (clearing it), it can be + subsequently set to a valid non-null string, restoring a branch value. +Execution: + Arrange: Build an InvocationContext using the builder with an initial branch of "initial-branch". + Call context.branch(null) to clear it. + Act: Call context.branch("restored-branch") on the InvocationContext. + Assert: Call context.branch() and assert that the returned Optional is present and its value + equals "restored-branch". +Validation: + This scenario validates that the branch field can transition through multiple states: set → + cleared → set again. This round-trip behavior is essential for agents that may need to reset + and reassign branch identifiers during complex, multi-step conversation flows. + +--- + +Scenario 8: Setting Branch with a Long String Value + +Details: + TestName: branchIsSetWithLongStringValue + Description: Verifies that the branch() method correctly handles a very long string input + without truncation, error, or modification. +Execution: + Arrange: Build an InvocationContext using the builder with default settings. + Create a long string of 1000 characters (e.g., repeated "a" characters). + Act: Call context.branch(longString) with the 1000-character string. + Assert: Call context.branch() and assert that the returned Optional is present and + its value equals the original 1000-character string in its entirety. +Validation: + The method does not impose any length restriction, so long strings should be stored verbatim. + This test guards against any unintended truncation logic being introduced in the future and + ensures robustness for systems that generate long or complex branch identifiers. + +--- + +Scenario 9: Branch Value Reflects the Last Set Value After Multiple Sequential Calls + +Details: + TestName: branchReflectsLastValueAfterMultipleSequentialUpdates + Description: Verifies that after multiple consecutive calls to branch() with different values, + only the last assigned value is retained. +Execution: + Arrange: Build an InvocationContext using the builder with default settings. + Act: Call context.branch("branch-1"), then context.branch("branch-2"), then + context.branch("branch-3") sequentially. + Assert: Call context.branch() and assert that the returned Optional contains "branch-3". +Validation: + Each call to branch() must fully replace the prior value. This confirms that the setter + semantics are correct (last-write-wins) and that no accumulation or stacking of branch values + occurs, which would be incorrect behavior for this mutable field. + +--- + +Scenario 10: Branch Value After copyOf Reflects Original Branch Before Mutation + +Details: + TestName: branchSetterOnCopiedContextDoesNotAffectOriginal + Description: Verifies that setting a new branch value on a copied InvocationContext (created + via copyOf) does not affect the branch value in the original context. +Execution: + Arrange: Build an InvocationContext with a branch value of "original-branch" via the builder. + Create a copy using InvocationContext.copyOf(originalContext). + Act: Call copiedContext.branch("modified-branch") on the copied context. + Assert: Assert that copiedContext.branch() returns Optional.of("modified-branch"), and + originalContext.branch() still returns Optional.of("original-branch"). +Validation: + copyOf creates a shallow copy of the context. Since branch is a private field (not a collection), + mutating it on the copy should not affect the original. This test confirms proper isolation + between the original and copied contexts, which is crucial for sub-agent delegation patterns + where copies of the context are passed and independently modified. + +*/ + +// ********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 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 Session mockSession; + + @Mock + private ResumabilityConfig mockResumabilityConfig; + + private static final String TEST_INVOCATION_ID = "test-invocation-id"; + + private static final String TEST_APP_NAME = "test-app"; + + private static final String TEST_USER_ID = "test-user"; + + private static final String TEST_SESSION_ID = "test-session-id"; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn(TEST_APP_NAME); + when(mockSession.userId()).thenReturn(TEST_USER_ID); + when(mockSession.id()).thenReturn(TEST_SESSION_ID); + } + + private InvocationContext buildDefaultContext() { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .build(); + } + + private InvocationContext buildContextWithBranch(String branch) { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .branch(Optional.ofNullable(branch)) + .build(); + } + + @Test + @Tag("valid") + void branchIsSetWithValidNonNullString() { + // Arrange + InvocationContext context = buildDefaultContext(); + String expectedBranch = "feature-branch-1"; + // Act + context.branch(expectedBranch); + // Assert + assertTrue(context.branch().isPresent(), "Branch Optional should be present after setting a non-null value"); + assertEquals(expectedBranch, context.branch().get(), "Branch value should equal the string that was set"); + } + + @Test + @Tag("invalid") + void branchIsSetToEmptyWhenNullIsPassed() { + // Arrange + InvocationContext context = buildContextWithBranch("old-branch"); + assertTrue(context.branch().isPresent(), "Precondition: branch should be set initially"); + // Act + context.branch(null); + // Assert + assertFalse(context.branch().isPresent(), "Branch Optional should be empty after setting null"); + } + + @Test + @Tag("valid") + void branchOverwritesPreviousNonNullValue() { + // Arrange + InvocationContext context = buildContextWithBranch("branch-v1"); + assertEquals("branch-v1", context.branch().orElse(null), "Precondition: initial branch should be branch-v1"); + // Act + context.branch("branch-v2"); + // Assert + assertTrue(context.branch().isPresent(), "Branch Optional should be present after overwrite"); + assertEquals("branch-v2", context.branch().get(), "Branch should contain the newly set value 'branch-v2'"); + assertNotEquals("branch-v1", context.branch().get(), + "Branch should no longer contain the old value 'branch-v1'"); + } + + @Test + @Tag("boundary") + void branchIsSetWithEmptyString() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch(""); + // Assert + assertTrue(context.branch().isPresent(), "Branch Optional should be present for an empty string (not null)"); + assertEquals("", context.branch().get(), "Branch value should be an empty string"); + } + + @Test + @Tag("boundary") + void branchIsSetWithWhitespaceOnlyString() { + // Arrange + InvocationContext context = buildDefaultContext(); + String whitespaceString = " "; + // Act + context.branch(whitespaceString); + // Assert + assertTrue(context.branch().isPresent(), "Branch Optional should be present for a whitespace-only string"); + assertEquals(whitespaceString, context.branch().get(), + "Branch value should equal the exact whitespace string without trimming"); + } + + @Test + @Tag("valid") + void branchSetterDoesNotAffectOtherContextFields() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(false) + .build(); + String capturedInvocationId = context.invocationId(); + boolean capturedEndInvocation = context.endInvocation(); + // Act + context.branch("new-branch"); + // Assert + assertEquals(Optional.of("new-branch"), context.branch(), "Branch should be updated to 'new-branch'"); + assertEquals(capturedInvocationId, context.invocationId(), + "invocationId should remain unchanged after setting branch"); + assertEquals(capturedEndInvocation, context.endInvocation(), + "endInvocation should remain unchanged after setting branch"); + assertSame(mockSessionService, context.sessionService(), + "sessionService should remain unchanged after setting branch"); + assertSame(mockArtifactService, context.artifactService(), + "artifactService should remain unchanged after setting branch"); + assertSame(mockSession, context.session(), "session should remain unchanged after setting branch"); + } + + @Test + @Tag("valid") + void branchTransitionsFromNullToValidString() { + // Arrange + InvocationContext context = buildContextWithBranch("initial-branch"); + context.branch(null); + assertFalse(context.branch().isPresent(), "Precondition: branch should be cleared after setting null"); + // Act + context.branch("restored-branch"); + // Assert + assertTrue(context.branch().isPresent(), "Branch Optional should be present after restoring a value"); + assertEquals("restored-branch", context.branch().get(), + "Branch value should be 'restored-branch' after restoration"); + } + + @Test + @Tag("boundary") + void branchIsSetWithLongStringValue() { + // Arrange + InvocationContext context = buildDefaultContext(); + String longString = "a".repeat(1000); + // Act + context.branch(longString); + // Assert + assertTrue(context.branch().isPresent(), "Branch Optional should be present for a long string"); + assertEquals(longString, context.branch().get(), + "Branch value should exactly match the 1000-character input string"); + assertEquals(1000, context.branch().get().length(), + "Branch string length should be 1000 characters without truncation"); + } + + @Test + @Tag("valid") + void branchReflectsLastValueAfterMultipleSequentialUpdates() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch("branch-1"); + context.branch("branch-2"); + context.branch("branch-3"); + // Assert + assertTrue(context.branch().isPresent(), "Branch Optional should be present after multiple updates"); + assertEquals("branch-3", context.branch().get(), "Branch should reflect the last assigned value 'branch-3'"); + } + + @Test + @Tag("integration") + void branchSetterOnCopiedContextDoesNotAffectOriginal() { + // Arrange + InvocationContext originalContext = buildContextWithBranch("original-branch"); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertEquals(Optional.of("original-branch"), originalContext.branch(), + "Precondition: original context should have 'original-branch'"); + assertEquals(Optional.of("original-branch"), copiedContext.branch(), + "Precondition: copied context should initially have 'original-branch'"); + // Act + copiedContext.branch("modified-branch"); + // Assert + assertEquals(Optional.of("modified-branch"), copiedContext.branch(), + "Copied context branch should be updated to 'modified-branch'"); + assertEquals(Optional.of("original-branch"), originalContext.branch(), + "Original context branch should remain 'original-branch' after mutation on copy"); + } + + @Test + @Tag("valid") + void branchGetterReturnsEmptyOptionalWhenNoBranchIsSet() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Assert + assertFalse(context.branch().isPresent(), + "Branch Optional should be empty when no branch was set during construction"); + } + + @Test + @Tag("valid") + void branchSetterStoresValueAsOptionalOfNullable() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch("test-branch"); + // Assert + assertEquals(Optional.of("test-branch"), context.branch(), + "branch() getter should return Optional.of('test-branch')"); + } + + @Test + @Tag("invalid") + void branchSetterWithNullOnDefaultContextResultsInEmptyOptional() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + context.branch(null); + // Assert + assertEquals(Optional.empty(), context.branch(), + "branch() getter should return Optional.empty() when null is passed"); + } + + @Test + @Tag("valid") + void branchBuilderAndSetterProduceSameResult() { + // Arrange + String branchValue = "test-branch-consistency"; + InvocationContext contextViaBuilder = buildContextWithBranch(branchValue); + InvocationContext contextViaSetter = buildDefaultContext(); + contextViaSetter.branch(branchValue); + // Assert + assertEquals(contextViaBuilder.branch(), contextViaSetter.branch(), + "Branch value set via builder and via setter should produce the same Optional result"); + } + +} 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 100644 index 000000000..196913689 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextBuilderTest.java @@ -0,0 +1,681 @@ +/* + * 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 Builder object, + confirming that the factory method is properly initialized and usable. +Execution: + Arrange: No special setup is required since builder() is a static factory method with no parameters. + Act: Call InvocationContext.builder() and store the result in a local variable of type InvocationContext.Builder. + Assert: Use assertNotNull to verify the returned Builder instance is not null. +Validation: + The assertion confirms that the builder() method always produces a valid, non-null Builder object. + This is the most fundamental contract of a factory method — it must never return null. + Failing this scenario would indicate a broken factory that could cause NullPointerExceptions throughout the application. + +--- + +Scenario 2: Verify That builder() Returns an Instance of Builder + +Details: + TestName: builderReturnsCorrectType + Description: Verifies that the object returned by builder() is specifically an instance of + InvocationContext.Builder and not some other type. +Execution: + Arrange: No special setup is required. + Act: Call InvocationContext.builder() and store the result. + Assert: Use assertInstanceOf (or assertTrue with instanceof) to verify the result is an instance of InvocationContext.Builder. +Validation: + The assertion confirms that the return type of builder() matches the declared return type InvocationContext.Builder. + This is important for type safety and ensures clients can safely chain builder methods without casting. + +--- + +Scenario 3: Verify That Each Call to builder() Returns a New Distinct Builder Instance + +Details: + TestName: builderReturnsNewInstanceOnEachCall + Description: Verifies that calling builder() multiple times produces separate, independent + Builder objects rather than a shared singleton. +Execution: + Arrange: No special setup is required. + Act: Call InvocationContext.builder() twice, storing the results as builder1 and builder2. + Assert: Use assertNotSame to verify that builder1 and builder2 are distinct object references. +Validation: + The assertion confirms that builder() creates a fresh Builder on every invocation. + If a singleton were returned, state set on one Builder would bleed into another, causing hard-to-diagnose + bugs when multiple InvocationContext instances are constructed concurrently or sequentially. + +--- + +Scenario 4: Verify That the Builder Produced by builder() Can Build a Minimal InvocationContext + +Details: + TestName: builderCanBuildInvocationContextWithMinimalConfiguration + Description: Verifies that the Builder returned by builder() is functional and can produce + an InvocationContext instance when only required fields are supplied (sessionService, + artifactService, agent, session), relying on default values for optional fields. +Execution: + Arrange: Create mock or stub instances of BaseSessionService, BaseArtifactService, BaseAgent, and Session. + Act: Call InvocationContext.builder(), chain the required setter methods + (sessionService, artifactService, agent, session), and call build(). + Assert: Use assertNotNull to verify the resulting InvocationContext object is not null. +Validation: + The assertion confirms that the Builder obtained from builder() supports the full builder-pattern + workflow and can produce a valid InvocationContext. This validates the end-to-end integration of + the factory method with the Builder class. + +--- + +Scenario 5: Verify That the Builder Returned by builder() Has Default liveRequestQueue as Empty Optional + +Details: + TestName: builderHasDefaultEmptyLiveRequestQueue + Description: Verifies that the Builder returned by builder() initializes liveRequestQueue to + Optional.empty() by default, so that building without explicitly setting this field + results in an InvocationContext whose liveRequestQueue() returns Optional.empty(). +Execution: + Arrange: Create mock or stub instances of BaseSessionService, BaseArtifactService, BaseAgent, and Session. + Act: Call InvocationContext.builder(), chain sessionService, artifactService, agent, session, + and build() without calling liveRequestQueue(). + Assert: Call liveRequestQueue() on the resulting InvocationContext and assert it equals Optional.empty(). +Validation: + The assertion verifies that the Builder's default initialization for liveRequestQueue is correct. + This matters because code that checks liveRequestQueue().isPresent() must not receive a null or + unexpected value when no live request queue is configured. + +--- + +Scenario 6: Verify That the Builder Returned by builder() Has Default branch as Empty Optional + +Details: + TestName: builderHasDefaultEmptyBranch + Description: Verifies that the Builder returned by builder() initializes the branch field to + Optional.empty() by default, resulting in an InvocationContext whose branch() + returns Optional.empty() when not explicitly set. +Execution: + Arrange: Create mock or stub instances of BaseSessionService, BaseArtifactService, BaseAgent, and Session. + Act: Call InvocationContext.builder(), chain sessionService, artifactService, agent, session, + and build() without calling branch(). + Assert: Call branch() on the resulting InvocationContext and assert it equals Optional.empty(). +Validation: + The assertion confirms the default branch value is Optional.empty(), ensuring that downstream + logic handling branched conversations will not encounter a null when no branch is set. + +--- + +Scenario 7: Verify That the Builder Returned by builder() Has Default endInvocation as False + +Details: + TestName: builderHasDefaultEndInvocationFalse + Description: Verifies that the Builder returned by builder() initializes endInvocation to false + by default, so that a newly built InvocationContext does not terminate prematurely + without an explicit signal. +Execution: + Arrange: Create mock or stub instances of BaseSessionService, BaseArtifactService, BaseAgent, and Session. + Act: Call InvocationContext.builder(), chain sessionService, artifactService, agent, session, + and build() without calling endInvocation(). + Assert: Call endInvocation() on the resulting InvocationContext and assert it is false. +Validation: + The assertion confirms that the default state of endInvocation is false, meaning invocations + proceed normally unless explicitly terminated. An incorrect default of true would cause all + invocations to terminate immediately upon creation. + +--- + +Scenario 8: Verify That the Builder Returned by builder() Has a Non-Null Default invocationId + +Details: + TestName: builderHasDefaultNonNullInvocationId + Description: Verifies that the Builder returned by builder() auto-generates a non-null, non-empty + invocationId (using the "e-" prefix UUID pattern) when no explicit invocationId is set. +Execution: + Arrange: Create mock or stub instances of BaseSessionService, BaseArtifactService, BaseAgent, and Session. + Act: Call InvocationContext.builder(), chain sessionService, artifactService, agent, session, + and build() without calling invocationId(). + Assert: Call invocationId() on the resulting InvocationContext and assert it is not null + and starts with the prefix "e-". +Validation: + The assertion verifies that every InvocationContext built through the default builder automatically + receives a unique identifier following the "e-{UUID}" format defined by newInvocationContextId(). + A missing invocationId would make tracing and debugging individual invocations impossible. + +--- + +Scenario 9: Verify That Two Calls to builder() Produce Builders With Different Default invocationIds + +Details: + TestName: builderGeneratesUniqueDefaultInvocationIds + Description: Verifies that successive calls to builder() each generate a unique default invocationId, + ensuring that two independently built InvocationContext objects do not share the same ID. +Execution: + Arrange: Create mock or stub instances of BaseSessionService, BaseArtifactService, BaseAgent, and Session. + Act: Build two InvocationContext instances using two separate calls to InvocationContext.builder(), + each chained with sessionService, artifactService, agent, session, and build(). + Assert: Call invocationId() on both contexts and assert that the two IDs are not equal to each other. +Validation: + The assertion confirms that the UUID-based auto-generation inside Builder is called fresh each time + builder() is invoked, producing globally unique IDs. Duplicate invocation IDs would cause conflicts + in event tracking, session management, and cost accounting. + +--- + +Scenario 10: Verify That the Builder Returned by builder() Has a Non-Null Default PluginManager + +Details: + TestName: builderHasDefaultNonNullPluginManager + Description: Verifies that the Builder returned by builder() initializes pluginManager with a + default non-null PluginManager instance when not explicitly set by the caller. +Execution: + Arrange: Create mock or stub instances of BaseSessionService, BaseArtifactService, BaseAgent, and Session. + Act: Call InvocationContext.builder(), chain sessionService, artifactService, agent, session, + and build() without calling pluginManager(). + Assert: Call pluginManager() on the resulting InvocationContext and assert it is not null. +Validation: + The assertion confirms that the Builder always provides a valid PluginManager even when none is + explicitly supplied, preventing NullPointerExceptions in plugin-related operations during an invocation. + +--- + +Scenario 11: Verify That the Builder Returned by builder() Has a Non-Null Default RunConfig + +Details: + TestName: builderHasDefaultNonNullRunConfig + Description: Verifies that the Builder returned by builder() initializes runConfig with a + default non-null RunConfig instance when not explicitly configured. +Execution: + Arrange: Create mock or stub instances of BaseSessionService, BaseArtifactService, BaseAgent, and Session. + Act: Call InvocationContext.builder(), chain sessionService, artifactService, agent, session, + and build() without calling runConfig(). + Assert: Call runConfig() on the resulting InvocationContext and assert it is not null. +Validation: + The assertion ensures that the default RunConfig is always present, preventing null reference + errors in methods like incrementLlmCallsCount() that depend on runConfig to enforce LLM call limits. + +--- + +Scenario 12: Verify That builder() Supports Full Method Chaining to Build a Complete InvocationContext + +Details: + TestName: builderSupportsFullMethodChainingForCompleteContext + Description: Verifies that the Builder returned by builder() supports fluent method chaining for + all available setter methods and produces a fully configured InvocationContext with + all fields set to non-default values. +Execution: + Arrange: Create mock or stub instances of BaseSessionService, BaseArtifactService, BaseMemoryService, + PluginManager, BaseAgent, Session, Content, RunConfig, ResumabilityConfig, and LiveRequestQueue. + Act: Call InvocationContext.builder() and chain all available builder methods: + sessionService(), artifactService(), memoryService(), pluginManager(), liveRequestQueue(LiveRequestQueue), + branch(String), invocationId(), agent(), session(), userContent(Content), runConfig(), + endInvocation(), resumabilityConfig(), and finally build(). + Assert: Use assertNotNull on the resulting InvocationContext. Verify individual fields using + the public accessor methods: sessionService(), artifactService(), memoryService(), + pluginManager(), liveRequestQueue(), branch(), invocationId(), agent(), session(), + userContent(), runConfig(), endInvocation(). +Validation: + The assertion confirms that all builder setter methods correctly pass values through to the + constructed InvocationContext, and that method chaining works seamlessly from builder() through + every setter to build(). This validates the completeness and correctness of the Builder pattern + as exposed through the builder() factory method. + +--- + +Scenario 13: Verify That the Builder Returned by builder() Allows userContent to Be Set as Empty Optional + +Details: + TestName: builderDefaultUserContentIsEmptyOptional + Description: Verifies that the Builder returned by builder() defaults userContent to Optional.empty(), + and the resulting InvocationContext reflects this when build() is called without + explicitly setting userContent. +Execution: + Arrange: Create mock or stub instances of BaseSessionService, BaseArtifactService, BaseAgent, and Session. + Act: Call InvocationContext.builder(), chain sessionService, artifactService, agent, session, + and build() without calling userContent(). + Assert: Call userContent() on the resulting InvocationContext and assert it equals Optional.empty(). +Validation: + The assertion confirms the safe default of Optional.empty() for userContent, which is critical for + live-streaming invocations where there may be no initial user content. Incorrect defaults could + cause NullPointerExceptions or incorrect behavior in content-handling logic. + +--- + +Scenario 14: Verify That builder() Is Accessible as a Static Method Without an Instance + +Details: + TestName: builderIsCallableAsStaticMethod + Description: Verifies that builder() can be called directly on the InvocationContext class without + requiring an existing InvocationContext instance, confirming its static nature. +Execution: + Arrange: No special setup is required. + Act: Call InvocationContext.builder() using the class reference (not an object reference). + Assert: Use assertNotNull to confirm a Builder is returned. +Validation: + This test confirms the static accessibility of the builder() method, which is essential for the + factory pattern to allow clients to construct InvocationContext instances without first needing + one already created. This is especially important in bootstrapping scenarios where no prior context exists. + +*/ + +// ********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 InvocationContextBuilderTest { + + @Mock + private BaseSessionService mockSessionService; + + @Mock + private BaseArtifactService mockArtifactService; + + @Mock + private BaseMemoryService mockMemoryService; + + @Mock + private BaseAgent mockAgent; + + @Mock + private Session mockSession; + + @Mock + private Content mockContent; + + @Mock + private RunConfig mockRunConfig; + + @Mock + private ResumabilityConfig mockResumabilityConfig; + + @Mock + private LiveRequestQueue mockLiveRequestQueue; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + } + + @Test + @Tag("valid") + void builderReturnsNonNullInstance() { + InvocationContext.Builder builder = InvocationContext.builder(); + assertNotNull(builder, "builder() should return a non-null Builder instance"); + } + + @Test + @Tag("valid") + void builderReturnsCorrectType() { + Object result = InvocationContext.builder(); + assertInstanceOf(InvocationContext.Builder.class, result, + "builder() should return an instance of InvocationContext.Builder"); + } + + @Test + @Tag("valid") + void builderReturnsNewInstanceOnEachCall() { + InvocationContext.Builder builder1 = InvocationContext.builder(); + InvocationContext.Builder builder2 = InvocationContext.builder(); + assertNotSame(builder1, builder2, "Each call to builder() should return a distinct Builder instance"); + } + + @Test + @Tag("valid") + void builderCanBuildInvocationContextWithMinimalConfiguration() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + assertNotNull(context, "Builder should produce a non-null InvocationContext with minimal configuration"); + } + + @Test + @Tag("valid") + void builderHasDefaultEmptyLiveRequestQueue() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + assertEquals(Optional.empty(), context.liveRequestQueue(), + "Default liveRequestQueue should be Optional.empty()"); + } + + @Test + @Tag("valid") + void builderHasDefaultEmptyBranch() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + assertEquals(Optional.empty(), context.branch(), "Default branch should be Optional.empty()"); + } + + @Test + @Tag("valid") + void builderHasDefaultEndInvocationFalse() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + assertFalse(context.endInvocation(), "Default endInvocation should be false"); + } + + @Test + @Tag("valid") + void builderHasDefaultNonNullInvocationId() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + String invocationId = context.invocationId(); + assertNotNull(invocationId, "Default invocationId should not be null"); + assertFalse(invocationId.isEmpty(), "Default invocationId should not be empty"); + assertTrue(invocationId.startsWith("e-"), + "Default invocationId should start with 'e-' prefix, but was: " + invocationId); + } + + @Test + @Tag("valid") + void builderGeneratesUniqueDefaultInvocationIds() { + 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(); + assertNotEquals(context1.invocationId(), context2.invocationId(), + "Two separately built InvocationContext instances should have different invocationIds"); + } + + @Test + @Tag("valid") + void builderHasDefaultNonNullPluginManager() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + assertNotNull(context.pluginManager(), "Default pluginManager should not be null"); + } + + @Test + @Tag("valid") + void builderHasDefaultNonNullRunConfig() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + assertNotNull(context.runConfig(), "Default runConfig should not be null"); + } + + @Test + @Tag("integration") + void builderSupportsFullMethodChainingForCompleteContext() { + String customInvocationId = "e-custom-invocation-id"; + String branchName = "agent1.agent2"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(new PluginManager()) + .liveRequestQueue(mockLiveRequestQueue) + .branch(branchName) + .invocationId(customInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(mockContent) + .runConfig(mockRunConfig) + .endInvocation(true) + .resumabilityConfig(mockResumabilityConfig) + .build(); + assertNotNull(context, "Fully chained builder should produce a non-null InvocationContext"); + assertSame(mockSessionService, context.sessionService(), "sessionService should match"); + assertSame(mockArtifactService, context.artifactService(), "artifactService should match"); + assertSame(mockMemoryService, context.memoryService(), "memoryService should match"); + assertNotNull(context.pluginManager(), "pluginManager should not be null"); + assertTrue(context.liveRequestQueue().isPresent(), "liveRequestQueue should be present"); + assertSame(mockLiveRequestQueue, context.liveRequestQueue().get(), "liveRequestQueue value should match"); + assertTrue(context.branch().isPresent(), "branch should be present"); + assertEquals(branchName, context.branch().get(), "branch value should match"); + assertEquals(customInvocationId, context.invocationId(), "invocationId should match"); + assertSame(mockAgent, context.agent(), "agent should match"); + assertSame(mockSession, context.session(), "session should match"); + assertTrue(context.userContent().isPresent(), "userContent should be present"); + assertSame(mockContent, context.userContent().get(), "userContent value should match"); + assertSame(mockRunConfig, context.runConfig(), "runConfig should match"); + assertTrue(context.endInvocation(), "endInvocation should be true"); + } + + @Test + @Tag("valid") + void builderDefaultUserContentIsEmptyOptional() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + assertEquals(Optional.empty(), context.userContent(), "Default userContent should be Optional.empty()"); + } + + @Test + @Tag("valid") + void builderIsCallableAsStaticMethod() { + InvocationContext.Builder builder = InvocationContext.builder(); + assertNotNull(builder, "builder() should be callable as a static method and return non-null"); + } + + @Test + @Tag("valid") + void builderSetsInvocationIdExplicitly() { + String customId = "e-my-custom-id"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId(customId) + .build(); + assertEquals(customId, context.invocationId(), + "Explicitly set invocationId should be preserved in built context"); + } + + @Test + @Tag("valid") + void builderSetsBranchExplicitly() { + String branch = "parentAgent.childAgent"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .branch(branch) + .build(); + assertTrue(context.branch().isPresent(), "branch should be present when explicitly set"); + assertEquals(branch, context.branch().get(), "branch value should match explicitly set value"); + } + + @Test + @Tag("valid") + void builderSetsEndInvocationTrue() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .endInvocation(true) + .build(); + assertTrue(context.endInvocation(), "endInvocation should be true when explicitly set to true"); + } + + @Test + @Tag("valid") + void builderSetsUserContentViaOptional() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .build(); + assertTrue(context.userContent().isPresent(), "userContent should be present when set via Optional"); + assertSame(mockContent, context.userContent().get(), "userContent value should match when set via Optional"); + } + + @Test + @Tag("valid") + void builderSetsLiveRequestQueueViaOptional() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .liveRequestQueue(Optional.of(mockLiveRequestQueue)) + .build(); + assertTrue(context.liveRequestQueue().isPresent(), "liveRequestQueue should be present when set via Optional"); + assertSame(mockLiveRequestQueue, context.liveRequestQueue().get(), "liveRequestQueue value should match"); + } + + @Test + @Tag("valid") + void builderSetsBranchViaOptional() { + String branchName = "rootAgent.subAgent"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .branch(Optional.of(branchName)) + .build(); + assertTrue(context.branch().isPresent(), "branch should be present when set via Optional"); + assertEquals(branchName, context.branch().get(), "branch value should match when set via Optional"); + } + + @Test + @Tag("valid") + void builderSetsMemoryService() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .memoryService(mockMemoryService) + .build(); + assertSame(mockMemoryService, context.memoryService(), "memoryService should match explicitly set value"); + } + + @Test + @Tag("valid") + void builderSetsPluginManagerExplicitly() { + PluginManager pluginManager = new PluginManager(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .pluginManager(pluginManager) + .build(); + assertSame(pluginManager, context.pluginManager(), "pluginManager should match explicitly set value"); + } + + @Test + @Tag("boundary") + void builderDefaultInvocationIdFormat() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .build(); + String invocationId = context.invocationId(); + assertNotNull(invocationId); + // Format: "e-" + UUID (UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) + assertTrue(invocationId.startsWith("e-"), + "invocationId must start with 'e-' prefix"); + String uuidPart = invocationId.substring(2); + assertFalse(uuidPart.isEmpty(), + "UUID part of invocationId should not be empty"); + // Check UUID length: 36 chars for standard UUID + assertEquals(36, uuidPart.length(), + "UUID part of invocationId should be 36 characters long"); + } + +@Test + @Tag("boundary") + void builderGeneratesMultipleUniqueInvocationIds() { + int iterations = 10; + java.util.Set ids = new java.util.HashSet<>(); + for (int i = 0; i < iterations; i++) { + 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 100644 index 000000000..1818fdcfe --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextCopyOfTest.java @@ -0,0 +1,464 @@ +/* + * 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 + +Details: + TestName: copyOfReturnsNewContextWithAllFieldsCopied + Description: Verifies that copyOf creates a new InvocationContext instance with all fields + (sessionService, artifactService, memoryService, pluginManager, liveRequestQueue, + branch, invocationId, agent, session, userContent, runConfig, endInvocation, + resumabilityConfig) copied from the original context. +Execution: + Arrange: + - Create mock instances of BaseSessionService, BaseArtifactService, BaseMemoryService, + PluginManager, LiveRequestQueue, BaseAgent, Session, Content, RunConfig, and ResumabilityConfig. + - Build an InvocationContext (original) using InvocationContext.builder() with all fields set + to the created mock instances, including a specific invocationId, branch, and endInvocation=true. + Act: + - Call InvocationContext.copyOf(original) to produce a newContext. + Assert: + - Use assertEquals or assertSame to verify that newContext.sessionService() == original.sessionService(). + - Use assertEquals or assertSame to verify that newContext.artifactService() == original.artifactService(). + - Use assertEquals or assertSame to verify that newContext.memoryService() == original.memoryService(). + - Use assertEquals or assertSame to verify that newContext.pluginManager() == original.pluginManager(). + - Use assertEquals to verify that newContext.liveRequestQueue() equals original.liveRequestQueue(). + - Use assertEquals to verify that newContext.branch() equals original.branch(). + - Use assertEquals to verify that newContext.invocationId() equals original.invocationId(). + - Use assertEquals or assertSame to verify that newContext.agent() == original.agent(). + - Use assertEquals or assertSame to verify that newContext.session() == original.session(). + - Use assertEquals to verify that newContext.userContent() equals original.userContent(). + - Use assertEquals or assertSame to verify that newContext.runConfig() == original.runConfig(). + - Use assertEquals to verify that newContext.endInvocation() == original.endInvocation(). +Validation: + Verifies that copyOf correctly performs a shallow copy of all fields from the original + InvocationContext, ensuring the new instance is initialized with identical references/values + for each property. This is fundamental for scenarios where an invocation context needs + to be forked without modifying the original. + +*/ + +// ********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 InvocationContextCopyOfTest { + + @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 Content mockContent; + + @Mock + private RunConfig mockRunConfig; + + @Mock + private ResumabilityConfig mockResumabilityConfig; + + @Mock + private LiveRequestQueue mockLiveRequestQueue; + + private static final String TEST_INVOCATION_ID = "test-invocation-id-123"; + + private static final String TEST_BRANCH = "agentA.agentB"; + + @BeforeEach + void setUp() { + // Common setup if needed + } + + // ------------------------------------------------------------------------- + // Scenario 1: Copy of a Fully Populated InvocationContext + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfReturnsNewContextWithAllFieldsCopied() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .liveRequestQueue(Optional.of(mockLiveRequestQueue)) + .branch(Optional.of(TEST_BRANCH)) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .endInvocation(true) + .resumabilityConfig(mockResumabilityConfig) + .build(); + // Act + InvocationContext newContext = InvocationContext.copyOf(original); + // Assert + assertNotNull(newContext, "copyOf should return a non-null context"); + assertNotSame(original, newContext, "copyOf should return a different instance"); + assertSame(mockSessionService, newContext.sessionService(), "sessionService should be same reference"); + assertSame(mockArtifactService, newContext.artifactService(), "artifactService should be same reference"); + assertSame(mockMemoryService, newContext.memoryService(), "memoryService should be same reference"); + assertSame(mockPluginManager, newContext.pluginManager(), "pluginManager should be same reference"); + assertEquals(original.liveRequestQueue(), newContext.liveRequestQueue(), "liveRequestQueue should be equal"); + assertEquals(original.branch(), newContext.branch(), "branch should be equal"); + assertEquals(TEST_INVOCATION_ID, newContext.invocationId(), "invocationId should match"); + assertSame(mockAgent, newContext.agent(), "agent should be same reference"); + assertSame(mockSession, newContext.session(), "session should be same reference"); + assertEquals(original.userContent(), newContext.userContent(), "userContent should be equal"); + assertSame(mockRunConfig, newContext.runConfig(), "runConfig should be same reference"); + assertEquals(true, newContext.endInvocation(), "endInvocation should match"); + } + + // ------------------------------------------------------------------------- + // Test: Copied context is a distinct (new) object + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfReturnsDistinctInstance() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .build(); + // Act + InvocationContext newContext = InvocationContext.copyOf(original); + // Assert + assertNotSame(original, newContext, "copyOf must return a new instance, not the same object"); + } + + // ------------------------------------------------------------------------- + // Test: Mutating the copy does not affect the original (endInvocation flag) + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfMutatingCopyDoesNotAffectOriginal() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + copy.setEndInvocation(true); + // Assert + assertFalse(original.endInvocation(), "Original endInvocation should remain false after mutating copy"); + assertTrue(copy.endInvocation(), "Copy's endInvocation should be updated to true"); + } + + // ------------------------------------------------------------------------- + // Test: Copy with endInvocation = false + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfPreservesEndInvocationFalse() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertFalse(copy.endInvocation(), "endInvocation=false should be preserved in the copy"); + } + + // ------------------------------------------------------------------------- + // Test: Copy with endInvocation = true + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfPreservesEndInvocationTrue() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .endInvocation(true) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertTrue(copy.endInvocation(), "endInvocation=true should be preserved in the copy"); + } + + // ------------------------------------------------------------------------- + // Test: Copy preserves empty optionals + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfPreservesEmptyOptionalFields() { + // 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 copy = InvocationContext.copyOf(original); + // Assert + assertFalse(copy.userContent().isPresent(), "Empty userContent should remain empty in copy"); + assertFalse(copy.branch().isPresent(), "Empty branch should remain empty in copy"); + } + + // ------------------------------------------------------------------------- + // Test: Copy with no liveRequestQueue + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfPreservesAbsentLiveRequestQueue() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertFalse(copy.liveRequestQueue().isPresent(), "Absent liveRequestQueue should remain absent in copy"); + } + + // ------------------------------------------------------------------------- + // Test: Copy with liveRequestQueue present + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfPreservesLiveRequestQueuePresent() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .liveRequestQueue(Optional.of(mockLiveRequestQueue)) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertTrue(copy.liveRequestQueue().isPresent(), "liveRequestQueue should be present in copy"); + assertSame(mockLiveRequestQueue, copy.liveRequestQueue().get(), "liveRequestQueue should be same reference"); + } + + // ------------------------------------------------------------------------- + // Test: Copy with branch present + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfPreservesBranch() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .branch(TEST_BRANCH) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertTrue(copy.branch().isPresent(), "branch should be present in copy"); + assertEquals(TEST_BRANCH, copy.branch().get(), "branch value should match original"); + } + + // ------------------------------------------------------------------------- + // Test: Copy propagates activeStreamingTools + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfCopiesActiveStreamingTools() { + // Arrange + ActiveStreamingTool mockActiveTool = mock(ActiveStreamingTool.class); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .build(); + original.activeStreamingTools().put("tool1", mockActiveTool); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertTrue(copy.activeStreamingTools().containsKey("tool1"), + "activeStreamingTools should be copied to the new context"); + assertSame(mockActiveTool, copy.activeStreamingTools().get("tool1"), + "activeStreamingTools value should be same reference"); + } + + // ------------------------------------------------------------------------- + // Test: Mutating activeStreamingTools on copy does not affect original + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void copyOfActiveStreamingToolsMutationDoesNotAffectOriginal() { + // Arrange + ActiveStreamingTool mockActiveTool = mock(ActiveStreamingTool.class); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + copy.activeStreamingTools().put("newTool", mockActiveTool); + // Assert + assertFalse(original.activeStreamingTools().containsKey("newTool"), + "Adding tools to copy should not affect original's activeStreamingTools"); + } + + // ------------------------------------------------------------------------- + // Test: Copy with empty activeStreamingTools + // ------------------------------------------------------------------------- + @Test + @Tag("boundary") + void copyOfWithEmptyActiveStreamingToolsIsEmpty() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertTrue(copy.activeStreamingTools().isEmpty(), + "activeStreamingTools in copy should be empty when original is empty"); + } + +// ------------------------------------------------------------------------- +// Test: Copy with multiple activeStreamingTools +// ------------------------------------------------------------------------- +@Test + @Tag("valid") + void copyOfCopiesMultipleActiveStreamingTools() { + // Arrange + ActiveStreamingTool mockActiveTool1 = mock(ActiveStreamingTool.class); + ActiveStreamingTool mockActiveTool2 = mock(ActiveStreamingTool.class); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .build(); + original.activeStreamingTools().put("tool1", mockActiveTool1); + original.activeStreamingTools().put("tool2", mockActiveTool2); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertEquals(2, copy.activeStreamingTools().size(), + "All activeStreamingTools should be copied"); + assertSame(mockActiveTool1, copy.activeStreamingTools().get("tool1")); + assertSame( \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextCreate98Test.java b/core/src/test/java/com/google/adk/agents/InvocationContextCreate98Test.java new file mode 100644 index 000000000..31923b9ff --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextCreate98Test.java @@ -0,0 +1,386 @@ +/* + * 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 + +Scenario 1: Create InvocationContext with all valid non-null parameters + +Details: + TestName: createWithAllValidNonNullParameters + Description: Verifies that the `create` method successfully builds and returns a valid + `InvocationContext` instance when all parameters, including a non-null + `LiveRequestQueue`, are provided with valid values. + +Execution: + Arrange: + - Create a mock of `BaseSessionService`. + - Create a mock of `BaseArtifactService`. + - Create a mock of `BaseAgent`. + - Create a mock of `Session`. + - Create a mock of `LiveRequestQueue`. + - Create a `RunConfig` instance using `RunConfig.builder().build()`. + + Act: + - Call `InvocationContext.create(sessionService, artifactService, agent, session, liveRequestQueue, runConfig)`. + + Assert: + - Assert that the returned `InvocationContext` instance is not null. + - Assert that `context.sessionService()` equals the mocked `sessionService`. + - Assert that `context.artifactService()` equals the mocked `artifactService`. + - Assert that `context.agent()` equals the mocked `agent`. + - Assert that `context.session()` equals the mocked `session`. + - Assert that `context.liveRequestQueue()` is present (i.e., `isPresent()` returns true). + - Assert that `context.liveRequestQueue().get()` equals the mocked `liveRequestQueue`. + - Assert that `context.runConfig()` equals the provided `runConfig`. + +Validation: + This test confirms that the factory method correctly delegates all provided parameters + to the builder and produces a fully populated `InvocationContext`. It is the happy-path + baseline for the method and ensures that no parameter is silently dropped during + construction. + +*/ + +// ********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.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 InvocationContextCreate98Test { + + @Mock + private BaseSessionService mockSessionService; + + @Mock + private BaseArtifactService mockArtifactService; + + @Mock + private BaseAgent mockAgent; + + @Mock + private Session mockSession; + + @Mock + private LiveRequestQueue mockLiveRequestQueue; + + private RunConfig runConfig; + + @BeforeEach + void setUp() { + runConfig = RunConfig.builder().build(); + } + + @Test + @Tag("valid") + void createWithAllValidNonNullParameters() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertEquals(mockSessionService, context.sessionService()); + assertEquals(mockArtifactService, context.artifactService()); + assertEquals(mockAgent, context.agent()); + assertEquals(mockSession, context.session()); + assertTrue(context.liveRequestQueue().isPresent()); + assertEquals(mockLiveRequestQueue, context.liveRequestQueue().get()); + assertEquals(runConfig, context.runConfig()); + } + + @Test + @Tag("valid") + void createWithNullLiveRequestQueueProducesEmptyOptional() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, null, runConfig); + assertNotNull(context); + assertFalse(context.liveRequestQueue().isPresent()); + assertEquals(mockSessionService, context.sessionService()); + assertEquals(mockArtifactService, context.artifactService()); + assertEquals(mockAgent, context.agent()); + assertEquals(mockSession, context.session()); + assertEquals(runConfig, context.runConfig()); + } + + @Test + @Tag("valid") + void createWithNullSessionServiceAllowsBuild() { + InvocationContext context = InvocationContext.create(null, mockArtifactService, mockAgent, mockSession, + mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertNull(context.sessionService()); + assertEquals(mockArtifactService, context.artifactService()); + assertEquals(mockAgent, context.agent()); + assertEquals(mockSession, context.session()); + assertTrue(context.liveRequestQueue().isPresent()); + assertEquals(runConfig, context.runConfig()); + } + + @Test + @Tag("valid") + void createWithNullArtifactServiceAllowsBuild() { + InvocationContext context = InvocationContext.create(mockSessionService, null, mockAgent, mockSession, + mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertEquals(mockSessionService, context.sessionService()); + assertNull(context.artifactService()); + assertEquals(mockAgent, context.agent()); + assertEquals(mockSession, context.session()); + assertTrue(context.liveRequestQueue().isPresent()); + assertEquals(runConfig, context.runConfig()); + } + + @Test + @Tag("valid") + void createWithNullAgentAllowsBuild() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, null, mockSession, + mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertEquals(mockSessionService, context.sessionService()); + assertEquals(mockArtifactService, context.artifactService()); + assertNull(context.agent()); + assertEquals(mockSession, context.session()); + assertTrue(context.liveRequestQueue().isPresent()); + assertEquals(runConfig, context.runConfig()); + } + + @Test + @Tag("valid") + void createWithNullSessionAllowsBuild() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, null, + mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertEquals(mockSessionService, context.sessionService()); + assertEquals(mockArtifactService, context.artifactService()); + assertEquals(mockAgent, context.agent()); + assertNull(context.session()); + assertTrue(context.liveRequestQueue().isPresent()); + assertEquals(runConfig, context.runConfig()); + } + + @Test + @Tag("valid") + void createWithNullRunConfigAllowsBuild() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, null); + assertNotNull(context); + assertEquals(mockSessionService, context.sessionService()); + assertEquals(mockArtifactService, context.artifactService()); + assertEquals(mockAgent, context.agent()); + assertEquals(mockSession, context.session()); + assertTrue(context.liveRequestQueue().isPresent()); + assertNull(context.runConfig()); + } + + @Test + @Tag("invalid") + void createWithAllNullParametersAllowsBuild() { + InvocationContext context = InvocationContext.create(null, null, null, null, null, null); + assertNotNull(context); + assertNull(context.sessionService()); + assertNull(context.artifactService()); + assertNull(context.agent()); + assertNull(context.session()); + assertFalse(context.liveRequestQueue().isPresent()); + assertNull(context.runConfig()); + } + + @Test + @Tag("valid") + void createReturnsNewInstanceEachTime() { + InvocationContext context1 = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + InvocationContext context2 = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotNull(context1); + assertNotNull(context2); + assertNotSame(context1, context2); + } + + @Test + @Tag("valid") + void createDoesNotSetInvocationId() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertNull(context.invocationId()); + } + + @Test + @Tag("valid") + void createDoesNotSetUserContent() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertFalse(context.userContent().isPresent()); + } + + @Test + @Tag("valid") + void createEndInvocationDefaultsFalse() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertFalse(context.endInvocation()); + } + + @Test + @Tag("valid") + void createMemoryServiceDefaultsNull() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertNull(context.memoryService()); + } + + @Test + @Tag("valid") + void createBranchDefaultsEmpty() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertFalse(context.branch().isPresent()); + } + + @Test + @Tag("valid") + void createActiveStreamingToolsDefaultsEmpty() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertNotNull(context.activeStreamingTools()); + assertTrue(context.activeStreamingTools().isEmpty()); + } + + @Test + @Tag("valid") + void createPluginManagerDefaultsNull() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotNull(context); + assertNull(context.pluginManager()); + } + + @Test + @Tag("integration") + void createWithMaxLlmCallsRunConfigSetsCorrectly() { + RunConfig customRunConfig = RunConfig.builder().setMaxLlmCalls(100).build(); + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, customRunConfig); + assertNotNull(context); + assertEquals(customRunConfig, context.runConfig()); + assertEquals(100, context.runConfig().maxLlmCalls()); + } + + @Test + @Tag("integration") + void createContextCanBeUsedForCopyOf() { + InvocationContext original = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + InvocationContext copy = InvocationContext.copyOf(original); + assertNotNull(copy); + assertEquals(original.sessionService(), copy.sessionService()); + assertEquals(original.artifactService(), copy.artifactService()); + assertEquals(original.agent(), copy.agent()); + assertEquals(original.session(), copy.session()); + assertEquals(original.liveRequestQueue(), copy.liveRequestQueue()); + assertEquals(original.runConfig(), copy.runConfig()); + } + + @Test + @Tag("valid") + void createWithDifferentSessionServiceProducesDifferentContext() { + BaseSessionService anotherSessionService = mock(BaseSessionService.class); + InvocationContext context1 = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + InvocationContext context2 = InvocationContext.create(anotherSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotEquals(context1.sessionService(), context2.sessionService()); + assertEquals(mockSessionService, context1.sessionService()); + assertEquals(anotherSessionService, context2.sessionService()); + } + + @Test + @Tag("boundary") + void createWithNullLiveRequestQueueHasEmptyOptionalNotNull() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, null, runConfig); + assertNotNull(context); + Optional queueOptional = context.liveRequestQueue(); + assertNotNull(queueOptional); + assertFalse(queueOptional.isPresent()); + } + + @Test + @Tag("boundary") + void createWithNonNullLiveRequestQueueHasPresentOptional() { + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertNotNull(context); + Optional queueOptional = context.liveRequestQueue(); + assertNotNull(queueOptional); + assertTrue(queueOptional.isPresent()); + assertSame(mockLiveRequestQueue, queueOptional.get()); + } + + @Test + @Tag("valid") + void createWithLiveRequestQueueWrapsItInOptional() { + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, liveRequestQueue, runConfig); + assertNotNull(context); + assertTrue(context.liveRequestQueue().isPresent()); + assertSame(liveRequestQueue, context.liveRequestQueue().get()); + } + + @Test + @Tag("integration") + void createContextEqualsAnotherContextWithSameParameters() { + InvocationContext context1 = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + InvocationContext context2 = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertEquals(context1, context2); + } + + @Test + @Tag("integration") + void createContextHashCodeConsistentWithEquals() { + InvocationContext context1 = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + InvocationContext context2 = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + mockSession, mockLiveRequestQueue, runConfig); + assertEquals(context1.hashCode(), context2.hashCode()); + } + +} 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 100644 index 000000000..282547ed2 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextCreateTest.java @@ -0,0 +1,447 @@ +/* + * 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: Create InvocationContext with All Valid Parameters Including Non-Null User Content + +Details: + TestName: createWithAllValidParametersAndNonNullUserContent + Description: Verifies that the `create` method successfully builds and returns an `InvocationContext` + instance when all parameters are valid and `userContent` is non-null. Confirms that + each field is correctly set on the returned object. + +Execution: + Arrange: + - Create a mock of `BaseSessionService`. + - Create a mock of `BaseArtifactService`. + - Define a non-null `invocationId` string (e.g., "test-invocation-001"). + - Create a mock of `BaseAgent`. + - Create a mock of `Session`. + - Create a non-null `Content` object as `userContent`. + - 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` is not null. + - Assert that `result.sessionService()` equals the provided `sessionService` mock. + - Assert that `result.artifactService()` equals the provided `artifactService` mock. + - Assert that `result.invocationId()` equals `"test-invocation-001"`. + - Assert that `result.agent()` equals the provided `agent` mock. + - Assert that `result.session()` equals the provided `session` mock. + - Assert that `result.userContent()` is present and equals the provided `Content` object. + - Assert that `result.runConfig()` equals the provided `runConfig`. + +Validation: + This test confirms that when all valid parameters are supplied, the factory method correctly + delegates to the builder and produces an `InvocationContext` where each field is populated + as expected. This is the primary happy-path test, ensuring the core construction logic works + end-to-end. + +*/ + +// ********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.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.adk.flows.llmflows.ResumabilityConfig; +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 InvocationContextCreateTest { + + @Mock + private BaseSessionService mockSessionService; + + @Mock + private BaseArtifactService mockArtifactService; + + @Mock + private BaseAgent mockAgent; + + @Mock + private Session mockSession; + + @Mock + private Content mockUserContent; + + private RunConfig runConfig; + + @BeforeEach + void setUp() { + runConfig = RunConfig.builder().build(); + } + + // ----------------------------------------------------------------------- + // Scenario 1: All valid parameters with non-null userContent + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createWithAllValidParametersAndNonNullUserContent() { + String invocationId = "test-invocation-001"; + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, invocationId, + mockAgent, mockSession, mockUserContent, runConfig); + assertNotNull(result); + assertEquals(mockSessionService, result.sessionService()); + assertEquals(mockArtifactService, result.artifactService()); + assertEquals(invocationId, result.invocationId()); + assertEquals(mockAgent, result.agent()); + assertEquals(mockSession, result.session()); + assertTrue(result.userContent().isPresent()); + assertEquals(mockUserContent, result.userContent().get()); + assertEquals(runConfig, result.runConfig()); + } + + // ----------------------------------------------------------------------- + // Null userContent wraps to Optional.empty() + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createWithNullUserContentWrapsToOptionalEmpty() { + String invocationId = "test-invocation-002"; + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, invocationId, + mockAgent, mockSession, null, runConfig); + assertNotNull(result); + assertFalse(result.userContent().isPresent()); + } + + // ----------------------------------------------------------------------- + // Session service is correctly stored + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createSetsSessionServiceCorrectly() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-003", + mockAgent, mockSession, mockUserContent, runConfig); + assertSame(mockSessionService, result.sessionService()); + } + + // ----------------------------------------------------------------------- + // Artifact service is correctly stored + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createSetsArtifactServiceCorrectly() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-004", + mockAgent, mockSession, mockUserContent, runConfig); + assertSame(mockArtifactService, result.artifactService()); + } + + // ----------------------------------------------------------------------- + // Invocation ID is correctly stored + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createSetsInvocationIdCorrectly() { + String expectedId = "specific-invocation-id-xyz"; + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, expectedId, + mockAgent, mockSession, mockUserContent, runConfig); + assertEquals(expectedId, result.invocationId()); + } + + // ----------------------------------------------------------------------- + // Agent is correctly stored + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createSetsAgentCorrectly() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-005", + mockAgent, mockSession, mockUserContent, runConfig); + assertSame(mockAgent, result.agent()); + } + + // ----------------------------------------------------------------------- + // Session is correctly stored + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createSetsSessionCorrectly() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-006", + mockAgent, mockSession, mockUserContent, runConfig); + assertSame(mockSession, result.session()); + } + + // ----------------------------------------------------------------------- + // RunConfig is correctly stored + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createSetsRunConfigCorrectly() { + RunConfig specificRunConfig = RunConfig.builder().setMaxLlmCalls(100).build(); + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-007", + mockAgent, mockSession, mockUserContent, specificRunConfig); + assertSame(specificRunConfig, result.runConfig()); + } + + // ----------------------------------------------------------------------- + // Null session service is accepted without throwing + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void createWithNullSessionServiceDoesNotThrow() { + assertDoesNotThrow(() -> InvocationContext.create(null, mockArtifactService, "inv-008", mockAgent, mockSession, + mockUserContent, runConfig)); + } + + // ----------------------------------------------------------------------- + // Null artifact service is accepted without throwing + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void createWithNullArtifactServiceDoesNotThrow() { + assertDoesNotThrow(() -> InvocationContext.create(mockSessionService, null, "inv-009", mockAgent, mockSession, + mockUserContent, runConfig)); + } + + // ----------------------------------------------------------------------- + // Null agent is accepted without throwing + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void createWithNullAgentDoesNotThrow() { + assertDoesNotThrow(() -> InvocationContext.create(mockSessionService, mockArtifactService, "inv-010", null, + mockSession, mockUserContent, runConfig)); + } + + // ----------------------------------------------------------------------- + // Null invocation ID is accepted without throwing + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void createWithNullInvocationIdDoesNotThrow() { + assertDoesNotThrow(() -> InvocationContext.create(mockSessionService, mockArtifactService, null, mockAgent, + mockSession, mockUserContent, runConfig)); + } + + // ----------------------------------------------------------------------- + // Null session is accepted without throwing + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void createWithNullSessionDoesNotThrow() { + assertDoesNotThrow(() -> InvocationContext.create(mockSessionService, mockArtifactService, "inv-011", mockAgent, + null, mockUserContent, runConfig)); + } + + // ----------------------------------------------------------------------- + // Null RunConfig is accepted without throwing + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void createWithNullRunConfigDoesNotThrow() { + assertDoesNotThrow(() -> InvocationContext.create(mockSessionService, mockArtifactService, "inv-012", mockAgent, + mockSession, mockUserContent, null)); + } + + // ----------------------------------------------------------------------- + // All-null parameters do not throw + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void createWithAllNullParametersDoesNotThrow() { + assertDoesNotThrow(() -> InvocationContext.create(null, null, null, null, null, null, null)); + } + + // ----------------------------------------------------------------------- + // Empty string invocation ID is stored as-is + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void createWithEmptyStringInvocationId() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "", mockAgent, + mockSession, mockUserContent, runConfig); + assertNotNull(result); + assertEquals("", result.invocationId()); + } + + // ----------------------------------------------------------------------- + // Very long invocation ID is stored correctly + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void createWithVeryLongInvocationId() { + String longId = "a".repeat(10_000); + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, longId, mockAgent, + mockSession, mockUserContent, runConfig); + assertNotNull(result); + assertEquals(longId, result.invocationId()); + } + + // ----------------------------------------------------------------------- + // memoryService defaults to null when not provided via this factory + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createReturnsContextWithNullMemoryServiceByDefault() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-013", + mockAgent, mockSession, mockUserContent, runConfig); + assertNull(result.memoryService()); + } + + // ----------------------------------------------------------------------- + // pluginManager defaults to null when not provided via this factory + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createReturnsContextWithNullPluginManagerByDefault() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-014", + mockAgent, mockSession, mockUserContent, runConfig); + assertNull(result.pluginManager()); + } + + // ----------------------------------------------------------------------- + // liveRequestQueue defaults to Optional.empty() when not provided + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createReturnsContextWithEmptyLiveRequestQueueByDefault() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-015", + mockAgent, mockSession, mockUserContent, runConfig); + assertNotNull(result.liveRequestQueue()); + assertFalse(result.liveRequestQueue().isPresent()); + } + + // ----------------------------------------------------------------------- + // branch defaults to Optional.empty() when not provided + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createReturnsContextWithEmptyBranchByDefault() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-016", + mockAgent, mockSession, mockUserContent, runConfig); + assertNotNull(result.branch()); + assertFalse(result.branch().isPresent()); + } + + // ----------------------------------------------------------------------- + // endInvocation defaults to false + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createReturnsContextWithEndInvocationFalseByDefault() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-017", + mockAgent, mockSession, mockUserContent, runConfig); + assertFalse(result.endInvocation()); + } + + // ----------------------------------------------------------------------- + // Two calls with same parameters produce equal but distinct instances + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void createTwiceWithSameParametersProducesEqualInstances() { + String invocationId = "inv-018"; + InvocationContext first = InvocationContext.create(mockSessionService, mockArtifactService, invocationId, + mockAgent, mockSession, mockUserContent, runConfig); + InvocationContext second = InvocationContext.create(mockSessionService, mockArtifactService, invocationId, + mockAgent, mockSession, mockUserContent, runConfig); + assertNotNull(first); + assertNotNull(second); + assertNotSame(first, second); + assertEquals(first, second); + } + + // ----------------------------------------------------------------------- + // Two calls with different invocation IDs produce unequal instances + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createWithDifferentInvocationIdsProducesUnequalInstances() { + InvocationContext first = InvocationContext.create(mockSessionService, mockArtifactService, "id-A", mockAgent, + mockSession, mockUserContent, runConfig); + InvocationContext second = InvocationContext.create(mockSessionService, mockArtifactService, "id-B", mockAgent, + mockSession, mockUserContent, runConfig); + assertNotEquals(first, second); + } + + // ----------------------------------------------------------------------- + // activeStreamingTools is non-null and empty by default + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createReturnsContextWithEmptyActiveStreamingToolsByDefault() { + InvocationContext result = InvocationContext.create(mockSessionService, mockArtifactService, "inv-019", + mockAgent, mockSession, mockUserContent, runConfig); + assertNotNull(result.activeStreamingTools()); + assertTrue(result.activeStreamingTools().isEmpty()); + } + + // ----------------------------------------------------------------------- + // maxLlmCalls is reflected in runConfig correctly + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void createWithCustomMaxLlmCallsRunConfigReflectsCorrectly() { + RunConfig customRunConfig = RunConfig.builder().setMaxLlmCalls(10).build(); + InvocationContext result = InvocationContext.create( + mockSessionService, + mockArtifactService, + "inv-020", + mockAgent, + mockSession, + mockUserContent, + customRunConfig); + assertNotNull(result); + assertEquals(10, result.runConfig().maxLlmCalls()); + } + +// ----------------------------------------------------------------------- +// Verifying that incrementLlmCallsCount throws when limit exceeded +// ----------------------------------------------------------------------- +@Test + @Tag("integration") + void incrementLlmCallsCountThrowsWhenMaxExceeded() { + RunConfig limitedConfig = RunConfig.builder().setMaxLlmCalls(1).build(); + InvocationContext ctx = InvocationContext.create( + mockSessionService, + mockArtifactService, + "inv-021", + mockAgent, + mockSession, + mockUserContent, + limitedConfig); + assertDoesNotThrow(ctx::incrementLlmCallsCount \ 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..b5240d962 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextEndInvocationTest.java @@ -0,0 +1,487 @@ +/* + * 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, confirming that the default value of the field is false. + +Execution: + Arrange: Create an InvocationContext instance using InvocationContext.builder() without + calling .endInvocation(true), providing the minimal required fields + (sessionService, artifactService, agent, session). + Act: Call endInvocation() on the constructed InvocationContext instance. + Assert: Use assertFalse() to verify that the returned value is false. + +Validation: + The assertion confirms that the default value of the endInvocation field is false, + which is the expected initial state for a newly created invocation context. This is + critical because it ensures that invocations are not prematurely terminated by default, + which would disrupt normal agent execution flow. + +*/ + +// ********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.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; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + when(mockSession.id()).thenReturn("testSessionId"); + } + + // ----------------------------------------------------------------------- + // Scenario 1: Default Value of endInvocation Returns False + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void endInvocationReturnsFalseByDefault() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-001") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .build(); + // Act + boolean result = context.endInvocation(); + // Assert + assertFalse(result); + } + + // ----------------------------------------------------------------------- + // Scenario 2: endInvocation Returns True When Set to True via Builder + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void endInvocationReturnsTrueWhenSetViaBuilder() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-002") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(true) + .build(); + // Act + boolean result = context.endInvocation(); + // Assert + assertTrue(result); + } + + // ----------------------------------------------------------------------- + // Scenario 3: endInvocation Returns False When Explicitly Set to False via Builder + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void endInvocationReturnsFalseWhenExplicitlySetFalseViaBuilder() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-003") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(false) + .build(); + // Act + boolean result = context.endInvocation(); + // Assert + assertFalse(result); + } + + // ----------------------------------------------------------------------- + // Scenario 4: setEndInvocation Changes State from False to True + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void setEndInvocationChangesFalseToTrue() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-004") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(false) + .build(); + assertFalse(context.endInvocation()); + // Act + context.setEndInvocation(true); + // Assert + assertTrue(context.endInvocation()); + } + + // ----------------------------------------------------------------------- + // Scenario 5: setEndInvocation Changes State from True to False + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void setEndInvocationChangesTrueToFalse() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-005") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(true) + .build(); + assertTrue(context.endInvocation()); + // Act + context.setEndInvocation(false); + // Assert + assertFalse(context.endInvocation()); + } + + // ----------------------------------------------------------------------- + // Scenario 6: setEndInvocation Called Multiple Times Reflects Latest Value + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void setEndInvocationCalledMultipleTimesReflectsLatestValue() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-006") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .build(); + // Act & Assert + assertFalse(context.endInvocation()); + context.setEndInvocation(true); + assertTrue(context.endInvocation()); + context.setEndInvocation(false); + assertFalse(context.endInvocation()); + context.setEndInvocation(true); + assertTrue(context.endInvocation()); + } + + // ----------------------------------------------------------------------- + // Scenario 7: copyOf Preserves endInvocation = false + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void copyOfPreservesEndInvocationFalse() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-007") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(false) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertFalse(copy.endInvocation()); + } + + // ----------------------------------------------------------------------- + // Scenario 8: copyOf Preserves endInvocation = true + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void copyOfPreservesEndInvocationTrue() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-008") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(true) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertTrue(copy.endInvocation()); + } + + // ----------------------------------------------------------------------- + // Scenario 9: Mutation on Copy Does Not Affect Original + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void mutatingCopyDoesNotAffectOriginalEndInvocation() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-009") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(false) + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + // Act + copy.setEndInvocation(true); + // Assert + assertTrue(copy.endInvocation()); + assertFalse(original.endInvocation()); + } + + // ----------------------------------------------------------------------- + // Scenario 10: Mutation on Original Does Not Affect Copy + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void mutatingOriginalDoesNotAffectCopyEndInvocation() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-010") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(false) + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + // Act + original.setEndInvocation(true); + // Assert + assertTrue(original.endInvocation()); + assertFalse(copy.endInvocation()); + } + + // ----------------------------------------------------------------------- + // Scenario 11: equals() Considers endInvocation Field + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void equalsReturnsFalseWhenEndInvocationDiffers() { + // Arrange + InvocationContext contextFalse = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-011") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(false) + .build(); + InvocationContext contextTrue = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-011") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(true) + .build(); + // Act & Assert + assertFalse(contextFalse.equals(contextTrue)); + assertFalse(contextTrue.equals(contextFalse)); + } + + // ----------------------------------------------------------------------- + // Scenario 12: equals() Returns True When endInvocation Matches + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void equalsReturnsTrueWhenEndInvocationMatches() { + // Arrange + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-012") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(true) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-012") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(true) + .build(); + // Act & Assert + assertTrue(context1.equals(context2)); + } + + // ----------------------------------------------------------------------- + // Scenario 13: Boundary - Set endInvocation True Then Immediately Read + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void endInvocationImmediatelyReflectsSetValue() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-013") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .build(); + // Act + context.setEndInvocation(true); + boolean resultAfterSet = context.endInvocation(); + // Assert + assertTrue(resultAfterSet); + } + + // ----------------------------------------------------------------------- + // Scenario 14: Boundary - Default State Without Any setEndInvocation Call + // ----------------------------------------------------------------------- + @Test + @Tag("boundary") + void endInvocationDefaultStateWithNoSetterCall() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-014") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .build(); + // Act - No call to setEndInvocation at all + boolean result = context.endInvocation(); + // Assert + assertFalse(result); + } + + // ----------------------------------------------------------------------- + // Scenario 15: create() Factory Method Produces Context with endInvocation False + // ----------------------------------------------------------------------- + @Test + @Tag("valid") + void createFactoryMethodProducesContextWithEndInvocationFalse() { + // Arrange + Content userContent = mock(Content.class); + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, "inv-015", + mockAgent, mockSession, userContent, runConfig); + // Act + boolean result = context.endInvocation(); + // Assert + assertFalse(result); + } + + // ----------------------------------------------------------------------- + // Scenario 16: Integration - setEndInvocation After copyOf Independently + // ----------------------------------------------------------------------- + @Test + @Tag("integration") + void setEndInvocationOnBothOriginalAndCopyAreIndependent() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("inv-016") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(null) + .endInvocation(false) + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + // Act + original.setEndInvocation(true); + copy.setEndInvocation(false); + // Assert + assertTrue(original.endInvocation()); + assertFalse(copy.endInvocation()); + } + +} 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..32dc4931f --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextEqualsTest.java @@ -0,0 +1,471 @@ +/* + * 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: equalsReturnsTrueWhenSameObjectReference + Description: Verifies that when the equals method is called with the same object reference (this == o), + it returns true immediately without further field comparison. This is the identity check + optimization at the top of the equals method. +Execution: + Arrange: Build a single InvocationContext instance using InvocationContext.builder() with + sessionService, artifactService, session, agent, and invocationId set. + Act: Call context.equals(context) passing the same object reference. + Assert: Assert that the result is true. +Validation: + This verifies the short-circuit identity check (this == o). If an object is compared to itself, + equality must always be true. This is a critical optimization and correctness guarantee for the + equals contract (reflexivity). + +*/ + +// ********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.assertNotEquals; +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.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.adk.models.LlmCallsLimitExceededException; +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 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; + + @Mock + private Content mockUserContent; + + private InvocationContext buildDefaultContext() { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + } + + @Test + @Tag("valid") + public void equalsReturnsTrueWhenSameObjectReference() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + boolean result = context.equals(context); + // Assert + assertTrue(result, "equals should return true when comparing the same object reference"); + } + + @Test + @Tag("valid") + public void equalsReturnsTrueWhenTwoContextsHaveSameFieldValues() { + // Arrange + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertEquals(context1, context2, "Two contexts with same field values should be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenComparedToNull() { + // Arrange + InvocationContext context = buildDefaultContext(); + // Act + boolean result = context.equals(null); + // Assert + assertFalse(result, "equals should return false when compared to null"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenComparedToDifferentType() { + // Arrange + InvocationContext context = buildDefaultContext(); + String differentObject = "not an InvocationContext"; + // Act + boolean result = context.equals(differentObject); + // Assert + assertFalse(result, "equals should return false when compared to a different type"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenInvocationIdDiffers() { + // Arrange + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("id-one") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("id-two") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different invocationId should not be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenEndInvocationDiffers() { + // Arrange + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .endInvocation(true) + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different endInvocation should not be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenSessionServiceDiffers() { + // Arrange + BaseSessionService anotherSessionService = mock(BaseSessionService.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(anotherSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different sessionService should not be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenArtifactServiceDiffers() { + // Arrange + BaseArtifactService anotherArtifactService = mock(BaseArtifactService.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(anotherArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different artifactService should not be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenMemoryServiceDiffers() { + // Arrange + BaseMemoryService anotherMemoryService = mock(BaseMemoryService.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(anotherMemoryService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different memoryService should not be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenPluginManagerDiffers() { + // Arrange + PluginManager anotherPluginManager = mock(PluginManager.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(mockPluginManager) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .pluginManager(anotherPluginManager) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different pluginManager should not be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenBranchDiffers() { + // Arrange + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .branch("branch-a") + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .branch("branch-b") + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different branch should not be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenAgentDiffers() { + // Arrange + BaseAgent anotherAgent = mock(BaseAgent.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(anotherAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different agent should not be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenSessionDiffers() { + // Arrange + Session anotherSession = mock(Session.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(anotherSession) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different session should not be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenUserContentDiffers() { + // Arrange + Content anotherContent = mock(Content.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(anotherContent)) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different userContent should not be equal"); + } + + @Test + @Tag("invalid") + public void equalsReturnsFalseWhenRunConfigDiffers() { + // Arrange + RunConfig anotherRunConfig = mock(RunConfig.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("same-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(anotherRunConfig) + .build(); + // Act & Assert + assertNotEquals(context1, context2, "Contexts with different runConfig should not be equal"); + } + +@Test + @Tag("valid") + public void equalsReturnsTrueWhenAllFieldsAreNull() { + // Arrange + InvocationContext context1 = InvocationContext.builder() + .build(); + InvocationContext context2 = InvocationContext.builder() + .build \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/agents/InvocationContextHashCodeTest.java b/core/src/test/java/com/google/adk/agents/InvocationContextHashCodeTest.java new file mode 100644 index 000000000..49c2d3743 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextHashCodeTest.java @@ -0,0 +1,478 @@ +/* + * 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=hashCode_c3a2d500cd +ROOST_METHOD_SIG_HASH=hashCode_5a2657087a + +Scenario 1: Hash Code Consistency for the Same Object Instance + +Details: + TestName: hashCodeIsConsistentForSameInstance + Description: Verifies that calling hashCode() multiple times on the same InvocationContext instance + returns the same integer value each time, satisfying the Java hashCode contract + of consistency within a single execution. + +Execution: + Arrange: + - Create a mock BaseSessionService (mockSessionService). + - Create a mock BaseArtifactService (mockArtifactService). + - Create a mock Session (mockSession). + - Build an InvocationContext using InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .session(mockSession) + .build(). + Act: + - Call context.hashCode() and store result as firstHash. + - Call context.hashCode() again and store result as secondHash. + - Call context.hashCode() a third time and store result as thirdHash. + Assert: + - assertEquals(firstHash, secondHash) + - assertEquals(secondHash, thirdHash) + +Validation: + This test ensures the fundamental Java hashCode contract: "whenever hashCode is invoked on the same + object more than once during an execution, it must consistently return the same integer." This is + critical for the correct behavior of hash-based collections like HashMap and HashSet where + InvocationContext instances may be used as keys. + +*/ + +// ********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.Objects; +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.UUID; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; + +@ExtendWith(MockitoExtension.class) +public class InvocationContextHashCodeTest { + + @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 RunConfig mockRunConfig; + + @Mock + private ResumabilityConfig mockResumabilityConfig; + + @Mock + private Content mockContent; + + @BeforeEach + void setUp() { + lenient().when(mockSession.appName()).thenReturn("testApp"); + lenient().when(mockSession.userId()).thenReturn("testUser"); + } + + // ------------------------------------------------------------------------- + // Scenario 1: Hash Code Consistency for the Same Object Instance + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeIsConsistentForSameInstance() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .session(mockSession) + .build(); + int firstHash = context.hashCode(); + int secondHash = context.hashCode(); + int thirdHash = context.hashCode(); + assertEquals(firstHash, secondHash); + assertEquals(secondHash, thirdHash); + } + + // ------------------------------------------------------------------------- + // Hash Code Equality: Two equal objects must have the same hash code + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeEqualForTwoEqualContexts() { + String invocationId = "inv-001"; + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId(invocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId(invocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + assertEquals(context1, context2, "Two contexts with same fields should be equal"); + assertEquals(context1.hashCode(), context2.hashCode(), "Equal objects must have the same hashCode"); + } + + // ------------------------------------------------------------------------- + // Hash Code Difference: Different invocationId should likely differ + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersForDifferentInvocationIds() { + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .session(mockSession) + .invocationId("inv-AAA") + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .session(mockSession) + .invocationId("inv-BBB") + .build(); + // Not strictly guaranteed, but highly likely for distinct strings + assertNotEquals(context1.hashCode(), context2.hashCode(), + "Different invocationIds should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code with All Null Optional / Null Fields + // ------------------------------------------------------------------------- + @Test + @Tag("boundary") + void hashCodeWithAllNullFieldsDoesNotThrow() { + InvocationContext context = InvocationContext.builder().build(); + assertDoesNotThrow(context::hashCode, "hashCode() must not throw when fields are null"); + } + + @Test + @Tag("boundary") + void hashCodeIsConsistentWithAllNullFields() { + InvocationContext context = InvocationContext.builder().build(); + int first = context.hashCode(); + int second = context.hashCode(); + assertEquals(first, second, "hashCode() must be consistent even when all fields are null"); + } + + // ------------------------------------------------------------------------- + // Hash Code with Only Session Set + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeConsistentWithOnlySessionSet() { + InvocationContext context = InvocationContext.builder().session(mockSession).build(); + int first = context.hashCode(); + int second = context.hashCode(); + assertEquals(first, second); + } + + // ------------------------------------------------------------------------- + // Hash Code changes when endInvocation flag differs + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersWhenEndInvocationDiffers() { + InvocationContext contextFalse = InvocationContext.builder() + .sessionService(mockSessionService) + .session(mockSession) + .invocationId("inv-001") + .endInvocation(false) + .build(); + InvocationContext contextTrue = InvocationContext.builder() + .sessionService(mockSessionService) + .session(mockSession) + .invocationId("inv-001") + .endInvocation(true) + .build(); + assertNotEquals(contextFalse.hashCode(), contextTrue.hashCode(), + "Different endInvocation values should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code changes when branch differs + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersWhenBranchDiffers() { + InvocationContext contextNoBranch = InvocationContext.builder() + .sessionService(mockSessionService) + .session(mockSession) + .invocationId("inv-001") + .build(); + InvocationContext contextWithBranch = InvocationContext.builder() + .sessionService(mockSessionService) + .session(mockSession) + .invocationId("inv-001") + .branch("agentA.agentB") + .build(); + assertNotEquals(contextNoBranch.hashCode(), contextWithBranch.hashCode(), + "Different branch values should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code changes when agent differs + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersWhenAgentDiffers() { + BaseAgent anotherAgent = mock(BaseAgent.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .session(mockSession) + .invocationId("inv-001") + .agent(mockAgent) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .session(mockSession) + .invocationId("inv-001") + .agent(anotherAgent) + .build(); + assertNotEquals(context1.hashCode(), context2.hashCode(), + "Different agent instances should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code changes when session differs + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersWhenSessionDiffers() { + Session anotherSession = mock(Session.class); + InvocationContext context1 = InvocationContext.builder().session(mockSession).invocationId("inv-001").build(); + InvocationContext context2 = InvocationContext.builder() + .session(anotherSession) + .invocationId("inv-001") + .build(); + assertNotEquals(context1.hashCode(), context2.hashCode(), + "Different session instances should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code changes when memoryService differs + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersWhenMemoryServiceDiffers() { + BaseMemoryService anotherMemoryService = mock(BaseMemoryService.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .memoryService(mockMemoryService) + .session(mockSession) + .invocationId("inv-001") + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .memoryService(anotherMemoryService) + .session(mockSession) + .invocationId("inv-001") + .build(); + assertNotEquals(context1.hashCode(), context2.hashCode(), + "Different memoryService instances should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code changes when pluginManager differs + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersWhenPluginManagerDiffers() { + PluginManager anotherPluginManager = mock(PluginManager.class); + InvocationContext context1 = InvocationContext.builder() + .session(mockSession) + .invocationId("inv-001") + .pluginManager(mockPluginManager) + .build(); + InvocationContext context2 = InvocationContext.builder() + .session(mockSession) + .invocationId("inv-001") + .pluginManager(anotherPluginManager) + .build(); + assertNotEquals(context1.hashCode(), context2.hashCode(), + "Different pluginManager instances should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code changes when userContent differs + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersWhenUserContentDiffers() { + Content anotherContent = mock(Content.class); + InvocationContext context1 = InvocationContext.builder() + .session(mockSession) + .invocationId("inv-001") + .userContent(Optional.of(mockContent)) + .build(); + InvocationContext context2 = InvocationContext.builder() + .session(mockSession) + .invocationId("inv-001") + .userContent(Optional.of(anotherContent)) + .build(); + assertNotEquals(context1.hashCode(), context2.hashCode(), + "Different userContent values should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code changes when runConfig differs + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersWhenRunConfigDiffers() { + RunConfig anotherRunConfig = mock(RunConfig.class); + InvocationContext context1 = InvocationContext.builder() + .session(mockSession) + .invocationId("inv-001") + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .session(mockSession) + .invocationId("inv-001") + .runConfig(anotherRunConfig) + .build(); + assertNotEquals(context1.hashCode(), context2.hashCode(), + "Different runConfig instances should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code with liveRequestQueue set vs not set + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersWhenLiveRequestQueueDiffers() { + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext contextWithQueue = InvocationContext.builder() + .session(mockSession) + .invocationId("inv-001") + .liveRequestQueue(Optional.of(liveRequestQueue)) + .build(); + InvocationContext contextWithoutQueue = InvocationContext.builder() + .session(mockSession) + .invocationId("inv-001") + .liveRequestQueue(Optional.empty()) + .build(); + assertNotEquals(contextWithQueue.hashCode(), contextWithoutQueue.hashCode(), + "Different liveRequestQueue values should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code with resumabilityConfig set vs not set + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeDiffersWhenResumabilityConfigDiffers() { + ResumabilityConfig anotherConfig = mock(ResumabilityConfig.class); + InvocationContext context1 = InvocationContext.builder() + .session(mockSession) + .invocationId("inv-001") + .resumabilityConfig(mockResumabilityConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .session(mockSession) + .invocationId("inv-001") + .resumabilityConfig(anotherConfig) + .build(); + assertNotEquals(context1.hashCode(), context2.hashCode(), + "Different resumabilityConfig instances should produce different hashCodes"); + } + + // ------------------------------------------------------------------------- + // Hash Code returns an int (not zero, for typical non-null inputs) + // ------------------------------------------------------------------------- + @Test + @Tag("valid") + void hashCodeReturnsNonZeroForTypicalContext() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId("inv-unique-123") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(mockContent)) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + // hashCode() should return a valid integer (not necessarily non-zero, but + // we verify it does not throw and returns a deterministic value) + assertDoesNotThrow(context::hashCode); + int hash = context.hashCode(); + assertEquals(hash, context.hashCode(), "hashCode must be deterministic"); + } + +// ------------------------------------------------------------------------- +// Hash Code matches Objects.hash computation +// ------------------------------------------------------------------------- +@Test + @Tag("valid") + void hashCodeMatchesExpectedObjectsHashComputation() { + String invocationId = "inv-match-test"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtif \ 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..62994b1ca --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextIncrementLlmCallsCountTest.java @@ -0,0 +1,318 @@ +/* + * 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 LLM Call Increment Without a Limit Configured + +Details: + TestName: incrementLlmCallsCountSucceedsWhenNoLimitConfigured + Description: Verifies that calling incrementLlmCallsCount() succeeds without throwing any exception + when the RunConfig has no maxLlmCalls limit set (i.e., maxLlmCalls is 0 or not configured). + This tests the happy path where the invocation cost manager increments the call count freely. + +Execution: + Arrange: + - Create a RunConfig using RunConfig.builder().build() (default configuration, no maxLlmCalls limit). + - Build an InvocationContext using InvocationContext.builder() with the required fields: + sessionService (mocked BaseSessionService), + artifactService (mocked BaseArtifactService), + session (mocked Session), + agent (mocked BaseAgent), + runConfig (the default RunConfig built above). + Act: + - Call invocationContext.incrementLlmCallsCount(). + Assert: + - No exception is thrown. + - The method completes normally. + +Validation: + This test confirms that when no LLM calls limit is enforced via RunConfig, the incrementLlmCallsCount() + method does not throw LlmCallsLimitExceededException. It ensures the default behavior allows unrestricted + LLM calls, which is critical for use cases where no rate limiting is required. + +*/ + +// ********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; + + @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 buildInvocationContext(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() { + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountSucceedsOnFirstCallWithLimit() { + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(5).build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountSucceedsMultipleTimesWithinLimit() throws LlmCallsLimitExceededException { + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(5).build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + for (int i = 0; i < 5; i++) { + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + } + } + + @Test + @Tag("invalid") + void incrementLlmCallsCountThrowsExceptionWhenLimitExceeded() throws LlmCallsLimitExceededException { + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(3).build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + invocationContext.incrementLlmCallsCount(); + invocationContext.incrementLlmCallsCount(); + invocationContext.incrementLlmCallsCount(); + assertThrows(LlmCallsLimitExceededException.class, () -> invocationContext.incrementLlmCallsCount()); + } + + @Test + @Tag("boundary") + void incrementLlmCallsCountSucceedsExactlyAtLimit() throws LlmCallsLimitExceededException { + int maxLlmCalls = 3; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxLlmCalls).build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + for (int i = 0; i < maxLlmCalls; i++) { + final int callNum = i; + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount(), + "Call " + (callNum + 1) + " should not throw"); + } + } + + @Test + @Tag("boundary") + void incrementLlmCallsCountThrowsOnFirstCallBeyondLimit() throws LlmCallsLimitExceededException { + int maxLlmCalls = 3; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxLlmCalls).build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + for (int i = 0; i < maxLlmCalls; i++) { + invocationContext.incrementLlmCallsCount(); + } + assertThrows(LlmCallsLimitExceededException.class, () -> invocationContext.incrementLlmCallsCount()); + } + + @Test + @Tag("boundary") + void incrementLlmCallsCountWithLimitOfOne() throws LlmCallsLimitExceededException { + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(1).build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + assertThrows(LlmCallsLimitExceededException.class, () -> invocationContext.incrementLlmCallsCount()); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountSucceedsWithNullRunConfig() { + InvocationContext invocationContext = buildInvocationContext(null); + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountSucceedsMultipleTimesWhenNoLimitConfigured() { + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + for (int i = 0; i < 100; i++) { + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + } + } + + @Test + @Tag("boundary") + void incrementLlmCallsCountWithMaxLlmCallsZeroAllowsUnlimitedCalls() { + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(0).build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + for (int i = 0; i < 50; i++) { + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + } + } + + @Test + @Tag("invalid") + void incrementLlmCallsCountExceptionMessageContainsLimit() throws LlmCallsLimitExceededException { + int maxLlmCalls = 2; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxLlmCalls).build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + invocationContext.incrementLlmCallsCount(); + invocationContext.incrementLlmCallsCount(); + LlmCallsLimitExceededException exception = assertThrows(LlmCallsLimitExceededException.class, + () -> invocationContext.incrementLlmCallsCount()); + assertNotNull(exception.getMessage()); + assertTrue(exception.getMessage().contains(String.valueOf(maxLlmCalls))); + } + + @Test + @Tag("integration") + void incrementLlmCallsCountWithCopiedContextTracksIndependently() throws LlmCallsLimitExceededException { + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(3).build(); + InvocationContext originalContext = buildInvocationContext(runConfig); + originalContext.incrementLlmCallsCount(); + originalContext.incrementLlmCallsCount(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertDoesNotThrow(() -> copiedContext.incrementLlmCallsCount()); + } + + @Test + @Tag("integration") + void incrementLlmCallsCountIsIsolatedBetweenContextInstances() throws LlmCallsLimitExceededException { + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(2).build(); + InvocationContext context1 = buildInvocationContext(runConfig); + InvocationContext context2 = buildInvocationContext(runConfig); + context1.incrementLlmCallsCount(); + context1.incrementLlmCallsCount(); + assertThrows(LlmCallsLimitExceededException.class, () -> context1.incrementLlmCallsCount()); + assertDoesNotThrow(() -> context2.incrementLlmCallsCount()); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountWithDefaultRunConfigBuilderAllowsMultipleCalls() { + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + for (int i = 0; i < 10; i++) { + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + } + } + + @Test + @Tag("boundary") + void incrementLlmCallsCountWithLargeLimit() throws LlmCallsLimitExceededException { + int maxLlmCalls = 1000; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxLlmCalls).build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + for (int i = 0; i < maxLlmCalls; i++) { + invocationContext.incrementLlmCallsCount(); + } + assertThrows(LlmCallsLimitExceededException.class, () -> invocationContext.incrementLlmCallsCount()); + } + + @Test + @Tag("invalid") + void incrementLlmCallsCountContinuouslyThrowsAfterLimitExceeded() throws LlmCallsLimitExceededException { + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(1).build(); + InvocationContext invocationContext = buildInvocationContext(runConfig); + invocationContext.incrementLlmCallsCount(); + assertThrows(LlmCallsLimitExceededException.class, () -> invocationContext.incrementLlmCallsCount()); + assertThrows(LlmCallsLimitExceededException.class, () -> invocationContext.incrementLlmCallsCount()); + assertThrows(LlmCallsLimitExceededException.class, () -> invocationContext.incrementLlmCallsCount()); + } + + @Test + @Tag("valid") + void incrementLlmCallsCountWithDefaultMaxLlmCallsIs500() throws LlmCallsLimitExceededException { + RunConfig runConfig = RunConfig.builder().build(); + assertEquals(500, runConfig.maxLlmCalls()); + InvocationContext invocationContext = buildInvocationContext(runConfig); + for (int i = 0; i < 500; i++) { + invocationContext.incrementLlmCallsCount(); + } + assertThrows(LlmCallsLimitExceededException.class, () -> invocationContext.incrementLlmCallsCount()); + } + + @Test + @Tag("integration") + void incrementLlmCallsCountWithFullyBuiltInvocationContext() throws LlmCallsLimitExceededException { + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(5).build(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId("full-context-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(runConfig) + .endInvocation(false) + .build(); + for (int i = 0; i < 5; i++) { + assertDoesNotThrow(() -> invocationContext.incrementLlmCallsCount()); + } + assertThrows(LlmCallsLimitExceededException.class, () -> invocationContext.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 100644 index 000000000..5e377f841 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextInvocationIdTest.java @@ -0,0 +1,429 @@ +/* + * 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: Retrieve an Explicitly Set Invocation ID + +Details: + TestName: invocationIdReturnsExplicitlySetValue + Description: Verifies that when an invocationId is explicitly provided via the Builder, + the invocationId() method returns exactly that same value without any + modification or transformation. + +Execution: + Arrange: Use InvocationContext.Builder to construct an InvocationContext instance, + setting invocationId to a specific known string value such as "test-invocation-123". + Provide all other required fields (sessionService, artifactService, agent, session) + with valid mock or stub objects. + Act: Call invocationId() on the constructed InvocationContext instance. + Assert: Assert that the returned String equals "test-invocation-123" using assertEquals. + +Validation: + The assertion verifies that the invocationId field is correctly stored and returned + by the accessor method without alteration. This is critical because downstream + components use the invocationId to correlate logs, events, and results to a specific + 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.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"); + } + + @Test + @Tag("valid") + void invocationIdReturnsExplicitlySetValue() { + String expectedInvocationId = "test-invocation-123"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + String actualInvocationId = context.invocationId(); + assertEquals(expectedInvocationId, actualInvocationId); + } + + @Test + @Tag("valid") + void invocationIdReturnsCorrectValueWhenSetWithUUID() { + String expectedInvocationId = UUID.randomUUID().toString(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + String actualInvocationId = context.invocationId(); + assertNotNull(actualInvocationId); + assertEquals(expectedInvocationId, actualInvocationId); + } + + @Test + @Tag("valid") + void invocationIdReturnsNullWhenNotSet() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + String actualInvocationId = context.invocationId(); + assertNull(actualInvocationId); + } + + @Test + @Tag("valid") + void invocationIdDoesNotModifyStoredValue() { + String expectedInvocationId = "e-abc-123-xyz"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + String firstCall = context.invocationId(); + String secondCall = context.invocationId(); + assertEquals(firstCall, secondCall); + assertEquals(expectedInvocationId, firstCall); + } + + @Test + @Tag("boundary") + void invocationIdReturnsEmptyString() { + String expectedInvocationId = ""; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + String actualInvocationId = context.invocationId(); + assertEquals(expectedInvocationId, actualInvocationId); + assertTrue(actualInvocationId.isEmpty()); + } + + @Test + @Tag("boundary") + void invocationIdReturnsValueWithSpecialCharacters() { + String expectedInvocationId = "e-!@#$%^&*()-invocation_ID.123"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + String actualInvocationId = context.invocationId(); + assertEquals(expectedInvocationId, actualInvocationId); + } + + @Test + @Tag("boundary") + void invocationIdReturnsVeryLongString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + sb.append("a"); + } + String expectedInvocationId = sb.toString(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + String actualInvocationId = context.invocationId(); + assertEquals(expectedInvocationId, actualInvocationId); + assertEquals(1000, actualInvocationId.length()); + } + + @Test + @Tag("valid") + void invocationIdIsConsistentAcrossMultipleCalls() { + String expectedInvocationId = "consistent-invocation-id"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + for (int i = 0; i < 10; i++) { + assertEquals(expectedInvocationId, context.invocationId()); + } + } + + @Test + @Tag("valid") + void invocationIdReturnedByCreateFactoryMethod() { + String expectedInvocationId = "factory-created-id-456"; + Content mockContent = mock(Content.class); + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, + expectedInvocationId, mockAgent, mockSession, mockContent, mockRunConfig); + String actualInvocationId = context.invocationId(); + assertEquals(expectedInvocationId, actualInvocationId); + } + + @Test + @Tag("valid") + void invocationIdReturnedFromCopiedContext() { + String expectedInvocationId = "original-invocation-id-789"; + 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); + assertEquals(expectedInvocationId, copiedContext.invocationId()); + } + + @Test + @Tag("integration") + void invocationIdIsPreservedAfterCopyOf() { + String expectedInvocationId = "invocation-to-copy-001"; + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertEquals(originalContext.invocationId(), copiedContext.invocationId()); + assertEquals(expectedInvocationId, copiedContext.invocationId()); + } + + @Test + @Tag("valid") + void invocationIdReturnsNewInvocationContextIdFormat() { + String generatedId = InvocationContext.newInvocationContextId(); + assertNotNull(generatedId); + assertTrue(generatedId.startsWith("e-")); + } + + @Test + @Tag("valid") + void invocationIdStoredWithNewInvocationContextIdIsReturned() { + String generatedId = InvocationContext.newInvocationContextId(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(generatedId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + String actualInvocationId = context.invocationId(); + assertNotNull(actualInvocationId); + assertEquals(generatedId, actualInvocationId); + assertTrue(actualInvocationId.startsWith("e-")); + } + + @Test + @Tag("valid") + void invocationIdWithNumericOnlyString() { + String expectedInvocationId = "1234567890"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + String actualInvocationId = context.invocationId(); + assertEquals(expectedInvocationId, actualInvocationId); + } + + @Test + @Tag("valid") + void invocationIdDoesNotAffectOtherFields() { + String expectedInvocationId = "id-for-field-isolation"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertEquals(expectedInvocationId, context.invocationId()); + assertNotNull(context.sessionService()); + assertNotNull(context.artifactService()); + assertNotNull(context.session()); + assertNotNull(context.agent()); + } + + @Test + @Tag("valid") + void invocationIdInDeprecatedConstructorIsRetained() { + String expectedInvocationId = "deprecated-constructor-id"; + InvocationContext context = new InvocationContext(mockSessionService, mockArtifactService, mockMemoryService, + mockPluginManager, Optional.empty(), Optional.empty(), expectedInvocationId, mockAgent, mockSession, + Optional.empty(), mockRunConfig, false); + assertEquals(expectedInvocationId, context.invocationId()); + } + + @Test + @Tag("boundary") + void invocationIdWithWhitespaceIsRetainedAsIs() { + String expectedInvocationId = " invocation with spaces "; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + String actualInvocationId = context.invocationId(); + assertEquals(expectedInvocationId, actualInvocationId); + } + + @Test + @Tag("valid") + void invocationIdIsIncludedInEqualsComparison() { + String invocationId1 = "id-one"; + String invocationId2 = "id-two"; + 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(); + assertNotEquals(context1.invocationId(), context2.invocationId()); + assertNotEquals(context1, context2); + } + + @Test + @Tag("integration") + void invocationIdIsRetainedAcrossBuilderChain() { + String expectedInvocationId = "builder-chain-id-999"; + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId(expectedInvocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + assertEquals(expectedInvocationId, context.invocationId()); + } + +} 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..c279b5f36 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextIsResumableTest.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=isResumable_2397941e46 +ROOST_METHOD_SIG_HASH=isResumable_6fb85ad5c9 + +Scenario 1: Default ResumabilityConfig Returns Expected Resumable Value + +Details: + TestName: isResumableWithDefaultResumabilityConfig + Description: Verifies that when an InvocationContext is built using the default ResumabilityConfig + (i.e., no explicit resumabilityConfig is set via the builder), the isResumable() + method returns the value dictated by the default ResumabilityConfig instance. + +Execution: + Arrange: + - Create a mock or stub for BaseSessionService, BaseArtifactService, Session. + - Build an InvocationContext using InvocationContext.builder() without explicitly setting + a ResumabilityConfig (allowing the builder to use its default: new ResumabilityConfig()). + - Note the expected value returned by the default ResumabilityConfig.isResumable(). + Act: + - Call invocationContext.isResumable() on the built InvocationContext. + Assert: + - Assert that the returned boolean matches the value returned by the default + ResumabilityConfig().isResumable(). + +Validation: + This test confirms that the builder's default ResumabilityConfig is correctly wired + into the InvocationContext and that isResumable() properly delegates to it without + any alteration. This is foundational to ensure that users who do not customize + resumability get a predictable default behavior. + +*/ + +// ********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 InvocationContextIsResumableTest { + + @Mock + private BaseSessionService mockSessionService; + + @Mock + private BaseArtifactService mockArtifactService; + + @Mock + private BaseMemoryService mockMemoryService; + + @Mock + private PluginManager mockPluginManager; + + @Mock + private Session mockSession; + + @Mock + private ResumabilityConfig mockResumabilityConfig; + + @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 isResumableWithDefaultResumabilityConfig() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + ResumabilityConfig defaultConfig = new ResumabilityConfig(); + boolean expectedResumable = defaultConfig.isResumable(); + // Act + boolean result = invocationContext.isResumable(); + // Assert + assertEquals(expectedResumable, result); + } + + @Test + @Tag("valid") + void isResumableReturnsTrueWhenResumabilityConfigReturnsTrue() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .build(); + // Act + boolean result = invocationContext.isResumable(); + // Assert + assertTrue(result); + verify(mockResumabilityConfig, times(1)).isResumable(); + } + + @Test + @Tag("valid") + void isResumableReturnsFalseWhenResumabilityConfigReturnsFalse() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .build(); + // Act + boolean result = invocationContext.isResumable(); + // Assert + assertFalse(result); + verify(mockResumabilityConfig, times(1)).isResumable(); + } + + @Test + @Tag("valid") + void isResumableDelegatesToResumabilityConfig() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .build(); + // Act + invocationContext.isResumable(); + // Assert - verify delegation happens exactly once + verify(mockResumabilityConfig).isResumable(); + } + + @Test + @Tag("valid") + void isResumableReturnsTrueWithExplicitResumableConfig() { + // Arrange + ResumabilityConfig resumabilityConfig = mock(ResumabilityConfig.class); + when(resumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id-explicit") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(resumabilityConfig) + .build(); + // Act + boolean result = invocationContext.isResumable(); + // Assert + assertTrue(result); + } + + @Test + @Tag("valid") + void isResumableReturnsFalseWithExplicitNonResumableConfig() { + // Arrange + ResumabilityConfig resumabilityConfig = mock(ResumabilityConfig.class); + when(resumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id-non-resumable") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(resumabilityConfig) + .build(); + // Act + boolean result = invocationContext.isResumable(); + // Assert + assertFalse(result); + } + + @Test + @Tag("valid") + void isResumableIsConsistentAcrossMultipleCalls() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id-multiple-calls") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .build(); + // Act + boolean firstCall = invocationContext.isResumable(); + boolean secondCall = invocationContext.isResumable(); + boolean thirdCall = invocationContext.isResumable(); + // Assert + assertEquals(firstCall, secondCall); + assertEquals(secondCall, thirdCall); + assertTrue(firstCall); + } + + @Test + @Tag("integration") + void isResumableWithCopyOfContextPreservesResumabilityConfig() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id-copy") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .build(); + // Act + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + boolean originalResult = originalContext.isResumable(); + boolean copiedResult = copiedContext.isResumable(); + // Assert + assertEquals(originalResult, copiedResult); + assertTrue(copiedResult); + } + + @Test + @Tag("integration") + void isResumableWithCopyOfContextPreservesNonResumabilityConfig() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id-copy-false") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .build(); + // Act + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + boolean originalResult = originalContext.isResumable(); + boolean copiedResult = copiedContext.isResumable(); + // Assert + assertEquals(originalResult, copiedResult); + assertFalse(copiedResult); + } + + @Test + @Tag("boundary") + void isResumableWithDefaultBuilderUsesDefaultResumabilityConfig() { + // Arrange - build without explicitly setting resumabilityConfig + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-default-resumability") + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act + boolean result = invocationContext.isResumable(); + // Assert - should not throw, result should be valid boolean + assertNotNull(result); + // The result is a primitive boolean so just checking it returns without exception + // Default ResumabilityConfig behavior is tested + boolean defaultExpected = new ResumabilityConfig().isResumable(); + assertEquals(defaultExpected, result); + } + + @Test + @Tag("valid") + void isResumableWithFullyPopulatedContextReturnsCorrectValue() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId("test-full-context") + .session(mockSession) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .endInvocation(false) + .branch(Optional.of("agentA.agentB")) + .build(); + // Act + boolean result = invocationContext.isResumable(); + // Assert + assertTrue(result); + verify(mockResumabilityConfig).isResumable(); + } + + @Test + @Tag("valid") + void isResumableWithCreateFactoryMethodDefaultsToDefaultResumabilityConfig() { + // Arrange + Content userContent = mock(Content.class); + InvocationContext invocationContext = InvocationContext.create(mockSessionService, mockArtifactService, + "test-invocation-id-factory", null, mockSession, userContent, mockRunConfig); + // Act + boolean result = invocationContext.isResumable(); + // Assert + boolean defaultExpected = new ResumabilityConfig().isResumable(); + assertEquals(defaultExpected, result); + } + + @Test + @Tag("boundary") + void isResumableReturnsBooleanPrimitive() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-primitive-boolean") + .session(mockSession) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .build(); + // Act + boolean result = invocationContext.isResumable(); + // Assert - verify it is indeed a primitive boolean (no null) + assertNotNull(Boolean.valueOf(result)); + assertTrue(result); + } + + @Test + @Tag("valid") + void isResumableInvocationContextWithNullUserContent() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-null-user-content") + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .build(); + // Act + boolean result = invocationContext.isResumable(); + // Assert + assertFalse(result); + } + + @Test + @Tag("valid") + void isResumableAfterSettingEndInvocationTrue() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-end-invocation") + .session(mockSession) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .endInvocation(true) + .build(); + // Act + boolean result = invocationContext.isResumable(); + // Assert - endInvocation should not affect resumability + assertTrue(result); + verify(mockResumabilityConfig).isResumable(); + } + + @Test + @Tag("integration") + void isResumableWithMockedResumabilityConfigChangingValues() { + // Arrange + when(mockResumabilityConfig.isResumable()) + .thenReturn(false) + .thenReturn(true); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-changing-values") + .session(mockSession) + .runConfig(mockRunConfig) + .resumabilityConfig(mockResumabilityConfig) + .build(); + // Act + boolean firstResult = invocationContext.isResumable(); + boolean secondResult = invocationContext.isResumable(); + // Assert - reflects the underlying config's behavior + assertFalse(firstResult); + assertTrue(secondResult); + } + +@Test + @Tag("valid") + void isResumableWithNewInvocationContextIdGeneratedCorrectly() { + // Arrange + when(mockResumabil \ 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 100644 index 000000000..3b3887aed --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextLiveRequestQueueTest.java @@ -0,0 +1,428 @@ +/* + * 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 LiveRequestQueue When It Is Explicitly Set via Builder + +Details: + TestName: liveRequestQueueReturnsPresentOptionalWhenSetViaBuilder + Description: Verifies that when a LiveRequestQueue instance is provided through the builder's + liveRequestQueue(LiveRequestQueue) method, the liveRequestQueue() accessor returns + an Optional containing that queue instance. + +Execution: + Arrange: + - Create a mock or concrete LiveRequestQueue instance. + - Build an InvocationContext using InvocationContext.builder() with the liveRequestQueue set + to the created LiveRequestQueue instance. + Act: + - Call liveRequestQueue() on the constructed InvocationContext. + Assert: + - Assert that the returned Optional is present (isPresent() returns true). + - Assert that the value inside the Optional is the same instance as the one provided to the builder. + +Validation: + This test confirms that when a live request queue is supplied during construction via the + non-deprecated builder method, the accessor correctly wraps it in a non-empty Optional and + returns the exact reference. This is critical for live/streaming agent invocations that rely + on the queue to process incoming messages. + +*/ + +// ********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 InvocationContextLiveRequestQueueTest { + + @Mock + private BaseSessionService mockSessionService; + + @Mock + private BaseArtifactService mockArtifactService; + + @Mock + private BaseMemoryService mockMemoryService; + + @Mock + private PluginManager mockPluginManager; + + @Mock + private BaseAgent mockAgent; + + private Session session; + + private RunConfig runConfig; + + @BeforeEach + void setUp() { + session = Session.builder("test-session-id").appName("test-app").userId("test-user").build(); + runConfig = RunConfig.builder().build(); + } + + @Test + @Tag("valid") + void liveRequestQueueReturnsPresentOptionalWhenSetViaBuilder() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .liveRequestQueue(liveRequestQueue) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent(), "LiveRequestQueue Optional should be present"); + assertSame(liveRequestQueue, result.get(), + "The LiveRequestQueue instance should be the same as the one provided to the builder"); + } + + @Test + @Tag("valid") + void liveRequestQueueReturnsEmptyOptionalWhenNotSet() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertFalse(result.isPresent(), "LiveRequestQueue Optional should be empty when not set"); + } + + @Test + @Tag("valid") + void liveRequestQueueReturnsEmptyOptionalWhenExplicitlySetToEmpty() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .liveRequestQueue(Optional.empty()) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertFalse(result.isPresent(), + "LiveRequestQueue Optional should be empty when explicitly set to Optional.empty()"); + } + + @Test + @Tag("valid") + void liveRequestQueueReturnsPresentOptionalWhenSetViaOptionalBuilder() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + Optional optionalQueue = Optional.of(liveRequestQueue); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .liveRequestQueue(optionalQueue) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent(), + "LiveRequestQueue Optional should be present when set via Optional builder method"); + assertSame(liveRequestQueue, result.get(), "The returned LiveRequestQueue should be the same instance"); + } + + @Test + @Tag("valid") + void liveRequestQueueReturnsConsistentResultOnMultipleCalls() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .liveRequestQueue(liveRequestQueue) + .runConfig(runConfig) + .build(); + // Act + Optional firstResult = context.liveRequestQueue(); + Optional secondResult = context.liveRequestQueue(); + // Assert + assertTrue(firstResult.isPresent(), "First call should return present Optional"); + assertTrue(secondResult.isPresent(), "Second call should return present Optional"); + assertSame(firstResult.get(), secondResult.get(), + "Multiple calls should return the same LiveRequestQueue instance"); + } + + @Test + @Tag("valid") + void liveRequestQueuePreservedInCopyOf() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .liveRequestQueue(liveRequestQueue) + .runConfig(runConfig) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertTrue(copy.liveRequestQueue().isPresent(), "Copied context should have present LiveRequestQueue"); + assertSame(original.liveRequestQueue().get(), copy.liveRequestQueue().get(), + "Copied context should have the same LiveRequestQueue instance"); + } + + @Test + @Tag("valid") + void liveRequestQueueAbsentInCopyOfWhenOriginalHasNone() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .runConfig(runConfig) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertFalse(copy.liveRequestQueue().isPresent(), + "Copied context should have absent LiveRequestQueue when original has none"); + } + + @Test + @Tag("valid") + void liveRequestQueueReturnsPresentWhenUsingStaticCreateWithLiveRequestQueue() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + session, liveRequestQueue, runConfig); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent(), + "LiveRequestQueue should be present when using static create with LiveRequestQueue"); + assertSame(liveRequestQueue, result.get(), "The LiveRequestQueue should be the same instance"); + } + + @Test + @Tag("valid") + void liveRequestQueueIsAbsentWhenCreatedViaStaticCreateWithoutLiveRequestQueue() { + // Arrange + Content userContent = Content.fromParts(); + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, + "test-invocation-id", mockAgent, session, userContent, runConfig); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertFalse(result.isPresent(), "LiveRequestQueue should be absent when created without a live request queue"); + } + + @Test + @Tag("boundary") + void liveRequestQueueReturnsPresentWhenStaticCreateReceivesNullLiveRequestQueue() { + // Arrange - null is passed, should result in empty Optional + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, mockAgent, + session, (LiveRequestQueue) null, runConfig); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertNotNull(result, "Result should not be null itself, should be an Optional"); + assertFalse(result.isPresent(), + "LiveRequestQueue Optional should be empty when null is passed to static create"); + } + + @Test + @Tag("valid") + void liveRequestQueueGetReturnsCorrectInstance() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .liveRequestQueue(liveRequestQueue) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent()); + LiveRequestQueue retrieved = result.get(); + assertNotNull(retrieved, "Retrieved LiveRequestQueue should not be null"); + assertEquals(liveRequestQueue, retrieved, "Retrieved LiveRequestQueue should equal the one provided"); + } + + @Test + @Tag("valid") + void liveRequestQueueIsIndependentFromOtherContextFields() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .agent(mockAgent) + .session(session) + .invocationId("test-id") + .liveRequestQueue(liveRequestQueue) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent(), "LiveRequestQueue should be present"); + assertSame(liveRequestQueue, result.get(), "Should return the exact instance"); + // Verify other fields are not affected + assertNotNull(context.sessionService(), "Session service should still be set"); + assertNotNull(context.session(), "Session should still be set"); + assertEquals("test-id", context.invocationId(), "Invocation ID should still be set"); + } + + @Test + @Tag("integration") + void liveRequestQueueCanReceiveContentAfterRetrieval() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .liveRequestQueue(liveRequestQueue) + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertTrue(result.isPresent()); + LiveRequestQueue retrievedQueue = result.get(); + // Verify that we can subscribe to the queue (integration-style check) + assertNotNull(retrievedQueue.get(), "The Flowable from queue should not be null"); + } + + @Test + @Tag("valid") + void liveRequestQueueNotPresentWhenOnlyBranchAndInvocationIdAreSet() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .branch("agent.subAgent") + .invocationId("some-invocation-id") + .runConfig(runConfig) + .build(); + // Act + Optional result = context.liveRequestQueue(); + // Assert + assertFalse(result.isPresent(), + "LiveRequestQueue should not be present when only branch and invocationId are set"); + } + + @Test + @Tag("boundary") + void liveRequestQueueResultIsImmutableAfterContextCreation() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .liveRequestQueue(liveRequestQueue) + .runConfig(runConfig) + .build(); + // Act - get the queue multiple times and verify it's the same wrapped optional + // behavior + Optional result1 = context.liveRequestQueue(); + Optional result2 = context.liveRequestQueue(); + // Assert + assertEquals(result1.isPresent(), result2.isPresent(), "Both calls should return Optional with same presence"); + assertSame(result1.get(), result2.get(), "Both calls should return the same LiveRequestQueue reference"); + } + + @Test + @Tag("valid") + void twoContextsWithDifferentLiveRequestQueuesReturnDifferentInstances() { + // Arrange + LiveRequestQueue queue1 = new LiveRequestQueue(); + LiveRequestQueue queue2 = new LiveRequestQueue(); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .liveRequestQueue(queue1) + .runConfig(runConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(session) + .liveRequestQueue(queue2) + .runConfig(runConfig) + .build(); + // Act + Optional result1 = context1.liveRequestQueue(); + Optional result2 = context2.liveRequestQueue(); + // Assert + assertTrue(result1.isPresent(), "First context's queue should be present"); + assertTrue(result2.isPresent(), "Second context's queue should be present"); + assertNotSame(result1.get(), result2.get(), + "Two different contexts should return different LiveRequestQueue instances"); + } + +} 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 100644 index 000000000..11980c9b5 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextMemoryServiceTest.java @@ -0,0 +1,363 @@ +/* + * 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: Return Non-Null Memory Service When Explicitly Set Via Builder + +Details: + TestName: memoryServiceReturnsNonNullInstanceWhenSetViaBuilder + Description: Verifies that the memoryService() method returns the exact BaseMemoryService instance + that was provided to the builder during construction of InvocationContext. This ensures + that the field assignment and retrieval work correctly end-to-end through the builder. +Execution: + Arrange: Create a mock or concrete implementation of BaseMemoryService. Use InvocationContext.builder() + and call .memoryService(mockedMemoryService) along with other required fields + (sessionService, artifactService, agent, session) to construct a valid InvocationContext instance. + Act: Call invocationContext.memoryService() on the constructed InvocationContext instance. + Assert: Use assertEquals or assertSame to verify that the returned BaseMemoryService instance + is the same object reference as the one passed into the builder. +Validation: + This assertion confirms that the private final field memoryService is correctly assigned during + construction and accurately returned by the accessor method. It is critical to verify that the + builder properly wires the dependency through to the final object, ensuring other components + that rely on memoryService() receive the correct service instance. + +*/ + +// ********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) +class InvocationContextMemoryServiceTest { + + @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 PluginManager mockPluginManager; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + } + + @Test + @Tag("valid") + void memoryServiceReturnsNonNullInstanceWhenSetViaBuilder() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseMemoryService result = context.memoryService(); + assertNotNull(result); + assertSame(mockMemoryService, result); + } + + @Test + @Tag("valid") + void memoryServiceReturnsSameReferenceAsProvided() { + BaseMemoryService anotherMockMemoryService = mock(BaseMemoryService.class); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(anotherMockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id-2") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseMemoryService result = context.memoryService(); + assertSame(anotherMockMemoryService, result); + } + + @Test + @Tag("valid") + void memoryServiceReturnsNullWhenNotSetViaBuilder() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id-3") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseMemoryService result = context.memoryService(); + assertNull(result); + } + + @Test + @Tag("valid") + void memoryServiceIsConsistentAcrossMultipleCalls() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("test-invocation-id-4") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseMemoryService firstCall = context.memoryService(); + BaseMemoryService secondCall = context.memoryService(); + BaseMemoryService thirdCall = context.memoryService(); + assertSame(firstCall, secondCall); + assertSame(secondCall, thirdCall); + assertSame(mockMemoryService, firstCall); + } + + @Test + @Tag("valid") + void memoryServiceReturnedFromCreateStaticFactoryMethod() { + Content userContent = mock(Content.class); + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, + "test-invocation-id-5", mockAgent, mockSession, userContent, mockRunConfig); + BaseMemoryService result = context.memoryService(); + assertNull(result); + } + + @Test + @Tag("valid") + void memoryServicePreservedInCopyOf() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("original-invocation-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertSame(mockMemoryService, copiedContext.memoryService()); + assertSame(originalContext.memoryService(), copiedContext.memoryService()); + } + + @Test + @Tag("valid") + void memoryServiceNotSharedBetweenDifferentContexts() { + BaseMemoryService firstMemoryService = mock(BaseMemoryService.class); + BaseMemoryService secondMemoryService = mock(BaseMemoryService.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(firstMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("invocation-id-1") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(secondMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("invocation-id-2") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(firstMemoryService, context1.memoryService()); + assertSame(secondMemoryService, context2.memoryService()); + assertNotSame(context1.memoryService(), context2.memoryService()); + } + + @Test + @Tag("valid") + void memoryServiceSetViaBuilderMethodIsImmutableAfterBuild() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("immutable-test-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + BaseMemoryService beforeModAttempt = context.memoryService(); + assertSame(mockMemoryService, beforeModAttempt); + assertSame(mockMemoryService, context.memoryService()); + } + + @Test + @Tag("integration") + void memoryServiceAccessibleAlongsideOtherServicesInContext() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .agent(mockAgent) + .session(mockSession) + .invocationId("integration-test-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertSame(mockMemoryService, context.memoryService()); + assertSame(mockSessionService, context.sessionService()); + assertSame(mockArtifactService, context.artifactService()); + assertSame(mockPluginManager, context.pluginManager()); + } + + @Test + @Tag("valid") + void memoryServicePreservedWhenBuilderOverridesOtherFields() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .branch(Optional.of("test-branch")) + .agent(mockAgent) + .session(mockSession) + .invocationId("branch-test-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + assertSame(mockMemoryService, context.memoryService()); + } + + @Test + @Tag("boundary") + void memoryServiceNullIsReturnedWhenBuilderDoesNotSetIt() { + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("null-memory-service-id") + .runConfig(mockRunConfig) + .build(); + assertNull(context.memoryService()); + } + + @Test + @Tag("integration") + void memoryServicePreservedAfterCopyOfWithNullOriginalMemoryService() { + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .agent(mockAgent) + .session(mockSession) + .invocationId("copy-null-memory-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertNull(copiedContext.memoryService()); + assertEquals(originalContext.memoryService(), copiedContext.memoryService()); + } + + @Test + @Tag("valid") + void memoryServiceEqualityInContextEqualsMethod() { + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("equals-test-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId("equals-test-id") + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + assertEquals(context1.memoryService(), context2.memoryService()); + } + + @Test + @Tag("valid") + void memoryServiceSetViaBuilderWithAllRequiredFields() { + String invocationId = "full-context-test-id"; + Content userContent = mock(Content.class); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .agent(mockAgent) + .session(mockSession) + .invocationId(invocationId) + .userContent(Optional.of(userContent)) + .runConfig(mockRunConfig) + .build(); + BaseMemoryService result = context.memoryService(); + assertNotNull(result); + assertSame(mockMemoryService, result); + assertEquals(invocationId, context.invocationId()); + } + +} 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..7f6ba2337 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextNewInvocationContextIdTest.java @@ -0,0 +1,261 @@ +/* + * 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 the Return Value Starts with the Expected Prefix + +Details: + TestName: returnValueStartsWithExpectedPrefix + Description: Verifies that the string returned by newInvocationContextId() always begins with the + fixed prefix "e-", which is the documented contract of this method. + +Execution: + Arrange: No setup is required since newInvocationContextId() is a static method with no + external dependencies. + Act: Invoke InvocationContext.newInvocationContextId() and store the result. + Assert: Use assertTrue(result.startsWith("e-")) to confirm the prefix is present. + +Validation: + The assertion ensures that the "e-" prefix is always prepended to the UUID. This prefix is + part of the invocation ID contract and is used throughout the system (e.g., in the Builder's + default invocationId field) to identify invocation contexts. Any deviation would break + downstream logic that relies on this naming convention. + +*/ + +// ********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 org.junit.jupiter.api.*; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +class InvocationContextNewInvocationContextIdTest { + + private static final String EXPECTED_PREFIX = "e-"; + + @Test + @Tag("valid") + void returnValueStartsWithExpectedPrefix() { + String result = InvocationContext.newInvocationContextId(); + assertNotNull(result, "newInvocationContextId() should not return null"); + assertTrue(result.startsWith(EXPECTED_PREFIX), "Expected result to start with 'e-' but was: " + result); + } + + @Test + @Tag("valid") + void returnValueIsNotNull() { + String result = InvocationContext.newInvocationContextId(); + assertNotNull(result, "newInvocationContextId() should never return null"); + } + + @Test + @Tag("valid") + void returnValueIsNotEmpty() { + String result = InvocationContext.newInvocationContextId(); + assertFalse(result.isEmpty(), "newInvocationContextId() should never return an empty string"); + } + + @Test + @Tag("valid") + void returnValueContainsValidUuidAfterPrefix() { + String result = InvocationContext.newInvocationContextId(); + assertTrue(result.startsWith(EXPECTED_PREFIX), "Result must start with 'e-' prefix"); + String uuidPart = result.substring(EXPECTED_PREFIX.length()); + assertDoesNotThrow(() -> UUID.fromString(uuidPart), + "The part after 'e-' prefix should be a valid UUID, but was: " + uuidPart); + } + + @Test + @Tag("valid") + void returnValueHasCorrectFormat() { + String result = InvocationContext.newInvocationContextId(); + assertNotNull(result); + assertTrue(result.startsWith(EXPECTED_PREFIX), "Result must start with 'e-'"); + String uuidPart = result.substring(EXPECTED_PREFIX.length()); + // UUID format: 8-4-4-4-12 hex chars separated by hyphens + assertTrue(uuidPart.matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"), + "UUID part should match standard UUID format, but was: " + uuidPart); + } + + @Test + @Tag("valid") + void returnValueHasExpectedLength() { + String result = InvocationContext.newInvocationContextId(); + // "e-" (2 chars) + UUID (36 chars) = 38 chars total + int expectedLength = EXPECTED_PREFIX.length() + 36; + assertEquals(expectedLength, result.length(), + "Expected length of " + expectedLength + " but was: " + result.length()); + } + + @Test + @Tag("valid") + void consecutiveCallsReturnDifferentValues() { + String result1 = InvocationContext.newInvocationContextId(); + String result2 = InvocationContext.newInvocationContextId(); + assertNotEquals(result1, result2, + "Consecutive calls to newInvocationContextId() should return different values"); + } + + @Test + @Tag("valid") + void multipleCallsAllStartWithPrefix() { + int callCount = 10; + for (int i = 0; i < callCount; i++) { + String result = InvocationContext.newInvocationContextId(); + assertTrue(result.startsWith(EXPECTED_PREFIX), + "Call #" + i + " did not start with 'e-', result was: " + result); + } + } + + @Test + @Tag("valid") + void multipleCallsProduceUniqueValues() { + int callCount = 100; + Set generatedIds = new HashSet<>(); + for (int i = 0; i < callCount; i++) { + String result = InvocationContext.newInvocationContextId(); + assertTrue(generatedIds.add(result), "Duplicate ID generated on call #" + i + ": " + result); + } + assertEquals(callCount, generatedIds.size(), "All generated IDs should be unique"); + } + + @Test + @Tag("valid") + void returnValuePrefixIsExactlyEDash() { + String result = InvocationContext.newInvocationContextId(); + assertEquals('e', result.charAt(0), "First character should be 'e'"); + assertEquals('-', result.charAt(1), "Second character should be '-'"); + } + + @Test + @Tag("boundary") + void uuidPartAfterPrefixHasCorrectLength() { + String result = InvocationContext.newInvocationContextId(); + String uuidPart = result.substring(EXPECTED_PREFIX.length()); + // Standard UUID string representation is 36 characters + assertEquals(36, uuidPart.length(), "UUID part should be 36 characters long, but was: " + uuidPart.length()); + } + + @Test + @Tag("boundary") + void returnValueDoesNotStartWithJustE() { + String result = InvocationContext.newInvocationContextId(); + assertFalse(result.startsWith("e") && !result.startsWith("e-"), "Result should start with 'e-' not just 'e'"); + } + + @Test + @Tag("boundary") + void returnValueDoesNotStartWithDashE() { + String result = InvocationContext.newInvocationContextId(); + assertFalse(result.startsWith("-e"), "Result should start with 'e-' not '-e'"); + } + + @Test + @Tag("valid") + void returnValueDoesNotContainOnlyPrefix() { + String result = InvocationContext.newInvocationContextId(); + assertNotEquals(EXPECTED_PREFIX, result, "Result should not be just the prefix 'e-'"); + } + + @Test + @Tag("valid") + void uuidPartIsVersion4Uuid() { + String result = InvocationContext.newInvocationContextId(); + String uuidPart = result.substring(EXPECTED_PREFIX.length()); + UUID uuid = UUID.fromString(uuidPart); + assertEquals(4, uuid.version(), "UUID should be version 4 (random), but was version: " + uuid.version()); + } + + @Test + @Tag("valid") + void uuidPartIsVariant2Uuid() { + String result = InvocationContext.newInvocationContextId(); + String uuidPart = result.substring(EXPECTED_PREFIX.length()); + UUID uuid = UUID.fromString(uuidPart); + assertEquals(2, uuid.variant(), "UUID should be variant 2 (IETF RFC 4122), but was variant: " + uuid.variant()); + } + + @Test + @Tag("valid") + void isStaticMethodCallableWithoutInstance() { + // Verify the method can be called statically without any instance setup + assertDoesNotThrow(() -> { + String result = InvocationContext.newInvocationContextId(); + assertNotNull(result); + }, "newInvocationContextId() should be callable as a static method without any setup"); + } + + @Test + @Tag("valid") + void returnValueDoesNotContainWhitespace() { + String result = InvocationContext.newInvocationContextId(); + assertFalse(result.contains(" "), "Result should not contain spaces"); + assertFalse(result.contains("\t"), "Result should not contain tabs"); + assertFalse(result.contains("\n"), "Result should not contain newlines"); + } + + @Test + @Tag("valid") + void prefixIsLowercaseE() { + String result = InvocationContext.newInvocationContextId(); + assertEquals('e', result.charAt(0), "Prefix should be lowercase 'e', not uppercase 'E'"); + } + + @Test + @Tag("integration") + void generatedIdCanBeUsedAsBuilderId() { + String generatedId = InvocationContext.newInvocationContextId(); + assertNotNull(generatedId, "Generated ID should not be null"); + assertTrue(generatedId.startsWith("e-"), "Generated ID used as invocation ID should start with 'e-'"); + // Verify the ID is a valid string that could be used in builders + assertFalse(generatedId.isEmpty(), "Generated ID should not be empty when used as invocation ID"); + } + + @Test + @Tag("valid") + void idHyphenSeparatesePrefixAndUuid() { + String result = InvocationContext.newInvocationContextId(); + // Verify structure: "e-" + UUID + String[] parts = result.split("-", 2); + assertEquals(2, parts.length, "Result should have at least one hyphen separating 'e' from UUID"); + assertEquals("e", parts[0], "First part before first hyphen should be 'e'"); + } + + @Test + @Tag("boundary") + void largeNumberOfCallsAllProduceValidIds() { + int callCount = 1000; + for (int i = 0; i < callCount; i++) { + String result = InvocationContext.newInvocationContextId(); + assertNotNull(result, "Call #" + i + " returned null"); + assertTrue(result.startsWith("e-"), "Call #" + i + " did not start with 'e-'"); + assertEquals(38, result.length(), "Call #" + i + " had unexpected length: " + result.length()); + } + } + +} 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 100644 index 000000000..ff49654bd --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextPluginManagerTest.java @@ -0,0 +1,380 @@ +/* + * 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 explicitly set PluginManager instance + +Details: + TestName: pluginManagerReturnsExplicitlySetInstance + Description: Verifies that when a specific PluginManager instance is provided to the builder, + the pluginManager() method returns exactly that same instance (reference equality). + +Execution: + Arrange: + - Create a specific PluginManager instance: PluginManager expectedPluginManager = new PluginManager() + - Build an InvocationContext using InvocationContext.builder() + .pluginManager(expectedPluginManager) + .build() + Act: + - Call invocationContext.pluginManager() + Assert: + - assertSame(expectedPluginManager, invocationContext.pluginManager()) + +Validation: + This assertion confirms that the pluginManager() accessor returns the exact same reference + that was injected during construction. Since pluginManager is a final field set once during + construction, the returned object must be identical to the one provided. This is critical + because consumers of InvocationContext rely on getting the correct plugin manager to access + tools and plugins during agent invocation. + +*/ + +// ********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 InvocationContextPluginManagerTest { + + @Mock + private BaseSessionService mockSessionService; + + @Mock + private BaseArtifactService mockArtifactService; + + @Mock + private BaseMemoryService mockMemoryService; + + @Mock + private BaseAgent mockAgent; + + private Session session; + + private RunConfig runConfig; + + @BeforeEach + void setUp() { + session = Session.builder("test-session-id").appName("test-app").userId("test-user").build(); + runConfig = RunConfig.builder().build(); + } + + @Test + @Tag("valid") + void pluginManagerReturnsExplicitlySetInstance() { + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(expectedPluginManager) + .build(); + PluginManager actual = invocationContext.pluginManager(); + assertSame(expectedPluginManager, actual); + } + + @Test + @Tag("valid") + void pluginManagerReturnsNullWhenNotSet() { + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + PluginManager actual = invocationContext.pluginManager(); + assertNull(actual); + } + + @Test + @Tag("valid") + void pluginManagerReturnsSameInstanceOnMultipleCalls() { + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(expectedPluginManager) + .build(); + PluginManager first = invocationContext.pluginManager(); + PluginManager second = invocationContext.pluginManager(); + assertSame(first, second); + assertSame(expectedPluginManager, first); + assertSame(expectedPluginManager, second); + } + + @Test + @Tag("valid") + void pluginManagerReturnedFromCopyOfIsSameInstance() { + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(expectedPluginManager) + .build(); + InvocationContext copied = InvocationContext.copyOf(original); + assertSame(expectedPluginManager, copied.pluginManager()); + } + + @Test + @Tag("valid") + void pluginManagerReturnedFromCopyOfWhenNullIsAlsoNull() { + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + InvocationContext copied = InvocationContext.copyOf(original); + assertNull(copied.pluginManager()); + } + + @Test + @Tag("valid") + void pluginManagerIsDistinctBetweenTwoContextsWithDifferentManagers() { + PluginManager pluginManager1 = new PluginManager(); + PluginManager pluginManager2 = new PluginManager(); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("invocation-1") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(pluginManager1) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("invocation-2") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(pluginManager2) + .build(); + assertSame(pluginManager1, context1.pluginManager()); + assertSame(pluginManager2, context2.pluginManager()); + assertNotSame(context1.pluginManager(), context2.pluginManager()); + } + + @Test + @Tag("valid") + void pluginManagerReturnedFromStaticCreateIsNull() { + Content userContent = Content.builder().role("user").build(); + InvocationContext invocationContext = InvocationContext.create(mockSessionService, mockArtifactService, + "test-invocation-id", mockAgent, session, userContent, runConfig); + assertNull(invocationContext.pluginManager()); + } + + @Test + @Tag("valid") + void pluginManagerNotNullWhenExplicitlyProvided() { + PluginManager pluginManager = new PluginManager(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(pluginManager) + .build(); + assertNotNull(invocationContext.pluginManager()); + } + + @Test + @Tag("valid") + void pluginManagerReturnedIsCorrectTypePluginManager() { + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(expectedPluginManager) + .build(); + PluginManager returned = invocationContext.pluginManager(); + assertNotNull(returned); + assertInstanceOf(PluginManager.class, returned); + } + + @Test + @Tag("integration") + void pluginManagerCanRegisterPluginsAfterRetrieval() { + PluginManager pluginManager = new PluginManager(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(pluginManager) + .build(); + PluginManager retrievedManager = invocationContext.pluginManager(); + assertNotNull(retrievedManager); + assertSame(pluginManager, retrievedManager); + } + + @Test + @Tag("integration") + void pluginManagerSameAcrossOriginalAndCopiedContext() { + PluginManager sharedPluginManager = new PluginManager(); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(sharedPluginManager) + .build(); + InvocationContext copied = InvocationContext.copyOf(original); + assertSame(original.pluginManager(), copied.pluginManager()); + assertSame(sharedPluginManager, original.pluginManager()); + assertSame(sharedPluginManager, copied.pluginManager()); + } + + @Test + @Tag("boundary") + void pluginManagerIsNullByDefaultWhenBuilderNotSet() { + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .build(); + assertNull(invocationContext.pluginManager()); + } + + @Test + @Tag("boundary") + void pluginManagerCanBeSetAfterOtherFieldsAreSet() { + PluginManager pluginManager = new PluginManager(); + InvocationContext invocationContext = InvocationContext.builder() + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .runConfig(runConfig) + .userContent(Optional.empty()) + .pluginManager(pluginManager) + .build(); + assertSame(pluginManager, invocationContext.pluginManager()); + } + + @Test + @Tag("valid") + void pluginManagerFieldNotModifiedByOtherContextOperations() { + PluginManager expectedPluginManager = new PluginManager(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(expectedPluginManager) + .build(); + invocationContext.setEndInvocation(true); + invocationContext.branch("new-branch"); + invocationContext.agent(mockAgent); + assertSame(expectedPluginManager, invocationContext.pluginManager()); + } + + @Test + @Tag("valid") + void pluginManagerEqualityBetweenContextsWithSamePluginManager() { + PluginManager sharedPluginManager = new PluginManager(); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("invocation-1") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(sharedPluginManager) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("invocation-1") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(runConfig) + .pluginManager(sharedPluginManager) + .build(); + assertSame(context1.pluginManager(), context2.pluginManager()); + } + +} 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..cc4e501a1 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextRunConfigTest.java @@ -0,0 +1,470 @@ +/* + * 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: Verify that runConfig() returns the exact RunConfig instance set via the builder + +Details: + TestName: runConfigReturnsExactInstanceSetViaBuilder + Description: Verifies that when an InvocationContext is constructed using the Builder with a specific + RunConfig instance, the runConfig() method returns the exact same instance that was provided. + +Execution: + Arrange: Create a RunConfig instance using RunConfig.builder().build(). Then build an InvocationContext + using InvocationContext.builder() with the created RunConfig set via .runConfig(runConfig). + Act: Call runConfig() on the constructed InvocationContext. + Assert: Use assertSame() or assertEquals() to confirm the returned RunConfig is identical to the one + provided during construction. + +Validation: + The assertion confirms that the runConfig field is stored and returned without any transformation or + wrapping. This is critical to ensure that the configuration controlling agent run behavior (e.g., LLM + call limits) is faithfully preserved and accessible 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.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 mockSession; + + private RunConfig defaultRunConfig; + + private static final String TEST_INVOCATION_ID = "test-invocation-id"; + + private static final String TEST_SESSION_ID = "test-session-id"; + + private static final String TEST_APP_NAME = "test-app"; + + private static final String TEST_USER_ID = "test-user"; + + @BeforeEach + void setUp() { + mockSession = Session.builder(TEST_SESSION_ID).appName(TEST_APP_NAME).userId(TEST_USER_ID).build(); + defaultRunConfig = RunConfig.builder().build(); + } + + @Test + @Tag("valid") + void runConfigReturnsExactInstanceSetViaBuilder() { + // Arrange + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + RunConfig result = context.runConfig(); + // Assert + assertSame(runConfig, result); + } + + @Test + @Tag("valid") + void runConfigReturnsNullWhenNotSet() { + // Arrange + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .build(); + // Act + RunConfig result = context.runConfig(); + // Assert + assertNull(result); + } + + @Test + @Tag("valid") + void runConfigReturnsCorrectConfigWithMaxLlmCallsSet() { + // Arrange + int maxCalls = 100; + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(maxCalls).build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + RunConfig result = context.runConfig(); + // Assert + assertNotNull(result); + assertSame(runConfig, result); + assertEquals(maxCalls, result.maxLlmCalls()); + } + + @Test + @Tag("valid") + void runConfigReturnsSameInstanceAfterCopyOf() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(200).build(); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + InvocationContext copied = InvocationContext.copyOf(original); + RunConfig result = copied.runConfig(); + // Assert + assertNotNull(result); + assertSame(runConfig, result); + assertEquals(original.runConfig(), copied.runConfig()); + } + + @Test + @Tag("valid") + void runConfigReturnsCorrectConfigCreatedViaStaticCreateMethod() { + // Arrange + RunConfig runConfig = RunConfig.builder().build(); + Content userContent = Content.newBuilder().build(); + InvocationContext context = InvocationContext.create(mockSessionService, mockArtifactService, + TEST_INVOCATION_ID, mockAgent, mockSession, userContent, runConfig); + // Act + RunConfig result = context.runConfig(); + // Assert + assertNotNull(result); + assertSame(runConfig, result); + } + + @Test + @Tag("valid") + void runConfigReturnsDefaultRunConfigWithDefaultValues() { + // Arrange + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + RunConfig result = context.runConfig(); + // Assert + assertNotNull(result); + assertEquals(500, result.maxLlmCalls()); + assertFalse(result.saveInputBlobsAsArtifacts()); + } + + @Test + @Tag("valid") + void runConfigReturnsSameReferenceOnMultipleCalls() { + // Arrange + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + RunConfig firstCall = context.runConfig(); + RunConfig secondCall = context.runConfig(); + // Assert + assertSame(firstCall, secondCall); + } + + @Test + @Tag("valid") + void runConfigReturnsCorrectConfigWithMemoryAndPluginManager() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(50).build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + RunConfig result = context.runConfig(); + // Assert + assertNotNull(result); + assertSame(runConfig, result); + assertEquals(50, result.maxLlmCalls()); + } + + @Test + @Tag("boundary") + void runConfigReturnsConfigWithMaxLlmCallsSetToOne() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(1).build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + RunConfig result = context.runConfig(); + // Assert + assertNotNull(result); + assertSame(runConfig, result); + assertEquals(1, result.maxLlmCalls()); + } + + @Test + @Tag("boundary") + void runConfigReturnsConfigWithMaxLlmCallsSetToZero() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(0).build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + RunConfig result = context.runConfig(); + // Assert + assertNotNull(result); + assertSame(runConfig, result); + assertEquals(0, result.maxLlmCalls()); + } + + @Test + @Tag("boundary") + void runConfigReturnsConfigWithMaxLlmCallsSetToIntegerMaxValue() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(Integer.MAX_VALUE).build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + RunConfig result = context.runConfig(); + // Assert + assertNotNull(result); + assertSame(runConfig, result); + assertEquals(Integer.MAX_VALUE, result.maxLlmCalls()); + } + + @Test + @Tag("valid") + void runConfigPreservedAfterBranchUpdate() { + // Arrange + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + context.branch("agent1.agent2"); + RunConfig result = context.runConfig(); + // Assert + assertNotNull(result); + assertSame(runConfig, result); + } + + @Test + @Tag("valid") + void runConfigPreservedAfterEndInvocationUpdate() { + // Arrange + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + context.setEndInvocation(true); + RunConfig result = context.runConfig(); + // Assert + assertNotNull(result); + assertSame(runConfig, result); + } + + @Test + @Tag("integration") + void runConfigIsConsistentAcrossOriginalAndCopiedContext() { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(300).build(); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + InvocationContext copied = InvocationContext.copyOf(original); + // Act + RunConfig originalRunConfig = original.runConfig(); + RunConfig copiedRunConfig = copied.runConfig(); + // Assert + assertNotNull(originalRunConfig); + assertNotNull(copiedRunConfig); + assertSame(originalRunConfig, copiedRunConfig); + assertEquals(300, originalRunConfig.maxLlmCalls()); + assertEquals(300, copiedRunConfig.maxLlmCalls()); + } + + @Test + @Tag("integration") + void runConfigIsUsedCorrectlyByIncrementLlmCallsCount() throws LlmCallsLimitExceededException { + // Arrange + RunConfig runConfig = RunConfig.builder().setMaxLlmCalls(2).build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act & Assert + assertSame(runConfig, context.runConfig()); + assertDoesNotThrow(() -> context.incrementLlmCallsCount()); + assertDoesNotThrow(() -> context.incrementLlmCallsCount()); + assertThrows(LlmCallsLimitExceededException.class, () -> context.incrementLlmCallsCount()); + } + + @Test + @Tag("valid") + void runConfigNotAffectedByAgentChange() { + // Arrange + RunConfig runConfig = RunConfig.builder().build(); + InvocationContext context = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig) + .build(); + // Act + BaseAgent anotherAgent = mock(BaseAgent.class); + context.agent(anotherAgent); + RunConfig result = context.runConfig(); + // Assert + assertNotNull(result); + assertSame(runConfig, result); + } + +@Test + @Tag("valid") + void runConfigEqualityCheckWithSameValues() { + // Arrange + RunConfig runConfig1 = RunConfig.builder().setMaxLlmCalls(100).build(); + RunConfig runConfig2 = RunConfig.builder().setMaxLlmCalls(100).build(); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig1) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(runConfig2) + .build(); + // Act + RunConfig result1 \ 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 100644 index 000000000..da7b0b337 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextSessionServiceTest.java @@ -0,0 +1,458 @@ +/* + * 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 same BaseSessionService + instance that was supplied to the builder during InvocationContext construction. + This confirms that the field is correctly assigned and returned without modification. +Execution: + Arrange: Create a mock or concrete implementation of BaseSessionService. Use + InvocationContext.builder() to construct an InvocationContext, supplying the + mocked BaseSessionService via builder.sessionService(...). Also supply a mock + Session and other required fields (e.g., artifactService, agent) to satisfy the + builder's needs. + Act: Call invocationContext.sessionService() on the built InvocationContext instance. + Assert: Use assertEquals or assertSame to verify that the returned BaseSessionService + is the same object reference as the one provided to the builder. +Validation: + This assertion confirms that the sessionService field is correctly wired through the + builder pattern and that sessionService() faithfully returns the injected dependency + without any transformation, wrapping, or replacement. This is critical because the + session service manages session state, and any mismatch in the instance could cause + incorrect session handling 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.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 InvocationContextSessionServiceTest { + + @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 RunConfig mockRunConfig; + + @Mock + private ResumabilityConfig mockResumabilityConfig; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + when(mockSession.id()).thenReturn("testSessionId"); + } + + @Test + @Tag("valid") + void sessionServiceReturnsInstanceSetViaBuilder() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertSame(mockSessionService, result, + "sessionService() should return the exact same instance set via the builder"); + } + + @Test + @Tag("valid") + void sessionServiceReturnsNullWhenNotSetInBuilder() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertNull(result, "sessionService() should return null when not set in the builder"); + } + + @Test + @Tag("valid") + void sessionServiceReturnsSameReferenceOnMultipleCalls() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + BaseSessionService firstCall = invocationContext.sessionService(); + BaseSessionService secondCall = invocationContext.sessionService(); + // Assert + assertSame(firstCall, secondCall, "sessionService() should return the same instance on multiple invocations"); + } + + @Test + @Tag("valid") + void sessionServiceReturnsDifferentInstancesForDifferentContexts() { + // Arrange + BaseSessionService mockSessionService2 = mock(BaseSessionService.class); + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("invocation-id-1") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext context2 = InvocationContext.builder() + .sessionService(mockSessionService2) + .artifactService(mockArtifactService) + .invocationId("invocation-id-2") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + BaseSessionService result1 = context1.sessionService(); + BaseSessionService result2 = context2.sessionService(); + // Assert + assertSame(mockSessionService, result1, "First context's sessionService should match the first mock"); + assertSame(mockSessionService2, result2, "Second context's sessionService should match the second mock"); + assertNotSame(result1, result2, "Different contexts should return different sessionService instances"); + } + + @Test + @Tag("valid") + void sessionServicePreservedAfterCopyOf() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + // Assert + assertSame(mockSessionService, copy.sessionService(), + "copyOf() should preserve the sessionService reference from the original context"); + } + + @Test + @Tag("valid") + void sessionServiceReturnedFromStaticCreateMethod() { + // Arrange + Content userContent = mock(Content.class); + InvocationContext invocationContext = InvocationContext.create(mockSessionService, mockArtifactService, + "test-invocation-id", mockAgent, mockSession, userContent, mockRunConfig); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertSame(mockSessionService, result, + "sessionService() should return the same instance when created via static create()"); + } + + @Test + @Tag("valid") + void sessionServiceNotNullWhenProvidedViaBuildMethod() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertNotNull(result, "sessionService() should not return null when a valid instance was provided"); + } + + @Test + @Tag("valid") + void sessionServiceEqualsOriginalMockInstance() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertEquals(mockSessionService, result, "sessionService() should be equal to the provided mock"); + } + + @Test + @Tag("integration") + void sessionServiceAccessibleAfterContextBuildWithAllFields() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + Content userContent = mock(Content.class); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .liveRequestQueue(Optional.of(liveRequestQueue)) + .branch(Optional.of("agentA.agentB")) + .invocationId("integration-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(userContent)) + .runConfig(mockRunConfig) + .endInvocation(false) + .build(); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertSame(mockSessionService, result, + "sessionService() should return the correct instance even when all fields are set"); + assertNotNull(result, "sessionService() should not be null in a fully-populated context"); + } + + @Test + @Tag("valid") + void sessionServiceIsIndependentOfOtherFieldChanges() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act: Change some mutable state + invocationContext.setEndInvocation(true); + invocationContext.branch("newBranch"); + invocationContext.agent(mockAgent); + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertSame(mockSessionService, result, "sessionService() should be unaffected by mutations to other fields"); + } + + @Test + @Tag("boundary") + void sessionServiceReturnedCorrectlyWithMinimalBuilderConfiguration() { + // Arrange - Minimal builder configuration with only session service and required + // fields + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .session(mockSession) + .build(); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertSame(mockSessionService, result, + "sessionService() should return the correct instance even with minimal builder configuration"); + } + + @Test + @Tag("valid") + void sessionServiceDoesNotInteractWithOtherServices() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertSame(mockSessionService, result); + verifyNoInteractions(mockArtifactService); + verifyNoInteractions(mockMemoryService); + } + + @Test + @Tag("valid") + void sessionServiceViaDeprecatedConstructorWithPluginManager() { + // Arrange - Using deprecated constructor that accepts PluginManager + InvocationContext invocationContext = new InvocationContext(mockSessionService, mockArtifactService, + mockMemoryService, mockPluginManager, Optional.empty(), Optional.empty(), "test-invocation-id", + mockAgent, mockSession, Optional.empty(), mockRunConfig, false); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertSame(mockSessionService, result, + "sessionService() should return the correct instance when using deprecated constructor with PluginManager"); + } + + @Test + @Tag("valid") + void sessionServiceViaDeprecatedConstructorWithoutPluginManager() { + // Arrange - Using deprecated constructor without PluginManager + InvocationContext invocationContext = new InvocationContext(mockSessionService, mockArtifactService, + mockMemoryService, Optional.empty(), Optional.empty(), "test-invocation-id", mockAgent, mockSession, + Optional.empty(), mockRunConfig, false); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertSame(mockSessionService, result, + "sessionService() should return the correct instance when using deprecated constructor without PluginManager"); + } + + @Test + @Tag("boundary") + void sessionServiceReturnedWhenOnlySessionServiceSetInBuilder() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder().sessionService(mockSessionService).build(); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertSame(mockSessionService, result, + "sessionService() should return the correct instance even when it's the only field set"); + } + + @Test + @Tag("valid") + void sessionServicePreservedInCopyDoesNotAffectOriginal() { + // Arrange + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("original-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + InvocationContext copy = InvocationContext.copyOf(original); + // Act + BaseSessionService originalResult = original.sessionService(); + BaseSessionService copyResult = copy.sessionService(); + // Assert + assertSame(originalResult, copyResult, + "Both original and copy should reference the same sessionService instance"); + assertSame(mockSessionService, originalResult, "Original context's sessionService should be the mock"); + assertSame(mockSessionService, copyResult, "Copy's sessionService should be the same mock"); + } + + @Test + @Tag("valid") + void sessionServiceReturnedFromStaticCreateWithLiveRequestQueue() { + // Arrange + LiveRequestQueue liveRequestQueue = new LiveRequestQueue(); + InvocationContext invocationContext = InvocationContext.create( + mockSessionService, + mockArtifactService, + mockAgent, + mockSession, + liveRequestQueue, + mockRunConfig + ); + // Act + BaseSessionService result = invocationContext.sessionService(); + // Assert + assertSame(mockSessionService, result, + "sessionService() should return the correct instance when created via create() with LiveRequestQueue"); + } + +@Test + @Tag("boundary") + void sessionServiceWithNullValuePassedToBuilder() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(null) + .artifactService(mockA \ No newline at end of file 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 100644 index 000000000..eaecb3b13 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextSessionTest.java @@ -0,0 +1,691 @@ +/* + * 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: Return the Session Object That Was Set During Construction via Builder + +Details: + TestName: sessionReturnsSessionSetViaBuilder + Description: Verifies that the `session()` method returns the exact same `Session` object + that was provided to the `InvocationContext` builder during construction. + This is the primary happy-path scenario confirming correct field assignment + and retrieval. +Execution: + Arrange: Create a mock or concrete `Session` object. Use `InvocationContext.builder()` + and call `.session(mockSession)` along with other minimally required fields + (e.g., `sessionService`, `artifactService`, `agent`, `invocationId`). + Call `.build()` to create the `InvocationContext` instance. + Act: Invoke `invocationContext.session()` on the constructed instance. + Assert: Use `assertSame(mockSession, invocationContext.session())` to confirm that + the returned object is the identical reference that was set. +Validation: + The assertion confirms that `session()` returns the exact reference stored in the + private final `session` field. Since the field is final and set only in the constructor, + the return value must always match what was provided at build time. This is fundamental + to ensuring that the invocation context correctly carries session state throughout an + agent's lifecycle. + +--- + +Scenario 2: Session Returned Is Not Null When a Valid Session Is Provided + +Details: + TestName: sessionReturnsNonNullWhenValidSessionProvided + Description: Verifies that calling `session()` on an `InvocationContext` built with a + valid (non-null) `Session` object returns a non-null value. This guards + against accidental null returns due to misconfigured construction logic. +Execution: + Arrange: Create a mock `Session` object. Build an `InvocationContext` via + `InvocationContext.builder().session(mockSession)...build()`. + Act: Call `invocationContext.session()`. + Assert: Use `assertNotNull(invocationContext.session())` to confirm the result is + not null. +Validation: + Ensures that a validly constructed `InvocationContext` never returns a null session. + Since `Session` is a required component for agent invocations (e.g., `appName()` and + `userId()` delegate to it), a null session would cause NullPointerExceptions throughout + the system. This test confirms the basic contract of the accessor method. + +--- + +Scenario 3: Session Returned Is Consistent Across Multiple Calls + +Details: + TestName: sessionReturnsSameInstanceOnMultipleCalls + Description: Verifies that repeated calls to `session()` on the same `InvocationContext` + instance always return the same `Session` object, confirming idempotency + and immutability of the private final `session` field. +Execution: + Arrange: Create a mock `Session` object. Build an `InvocationContext` instance using + the builder with that session. + Act: Call `invocationContext.session()` multiple times (e.g., three times) and store + each result. + Assert: Use `assertSame(firstResult, secondResult)` and `assertSame(firstResult, thirdResult)` + to confirm all calls return the identical reference. +Validation: + Since `session` is a `private final` field, its value cannot change after construction. + This test verifies the immutability guarantee, ensuring that callers can safely cache + or rely on the session reference without concern for it changing between accesses. + This is critical in multi-step agent workflows where session context must remain stable. + +--- + +Scenario 4: Session Returned Matches the Session Used in copyOf + +Details: + TestName: sessionReturnsSameSessionAfterCopyOf + Description: Verifies that when `InvocationContext.copyOf(other)` is used to create a + shallow copy, the `session()` method on the copied context returns the same + `Session` instance as the original context. This confirms that `copyOf` + correctly propagates the session reference. +Execution: + Arrange: Create a mock `Session` object. Build an original `InvocationContext` with + that session. Call `InvocationContext.copyOf(originalContext)` to create + a copied context. + Act: Call `copiedContext.session()` on the new context. + Assert: Use `assertSame(originalContext.session(), copiedContext.session())` to confirm + both return the same `Session` reference. +Validation: + The `copyOf` method performs a shallow copy and explicitly passes `other.session` to the + builder. This test validates that the session propagation in `copyOf` works correctly. + In agent delegation scenarios where sub-contexts are created from parent contexts, it is + essential that the same session is shared to maintain conversational coherence. + +--- + +Scenario 5: Session Object Returned Reflects the Correct Application Name via appName() + +Details: + TestName: sessionReturnedAllowsAccessToAppName + Description: Verifies that the `Session` object returned by `session()` is functional + and correctly exposes the application name. This test validates the + integration between the `session()` accessor and the `appName()` method, + which internally delegates to `session.appName()`. +Execution: + Arrange: Create a mock `Session` object configured to return a specific app name + (e.g., "test-app") when `appName()` is called. Build an `InvocationContext` + with this mock session. + Act: Retrieve the session via `invocationContext.session()` and call `appName()` on + the returned session. Also call `invocationContext.appName()` directly. + Assert: Use `assertEquals("test-app", invocationContext.session().appName())` and + `assertEquals(invocationContext.session().appName(), invocationContext.appName())` + to confirm consistency. +Validation: + The `appName()` method on `InvocationContext` delegates to `session.appName()`, + confirming that the session returned by `session()` is the live, functional object + used by the context. This test verifies the correctness of the delegation chain and + ensures the session is not a stale or incorrectly wrapped copy. + +--- + +Scenario 6: Session Object Returned Reflects the Correct User ID via userId() + +Details: + TestName: sessionReturnedAllowsAccessToUserId + Description: Verifies that the `Session` object returned by `session()` correctly + exposes the user ID. This validates that the same session object used + internally by `userId()` delegation is accessible via `session()`. +Execution: + Arrange: Create a mock `Session` configured to return a specific user ID + (e.g., "user-123") when `userId()` is called. Build an `InvocationContext` + using this session. + Act: Call `invocationContext.session()` to get the session, then call `userId()` on it. + Also call `invocationContext.userId()` directly. + Assert: Use `assertEquals("user-123", invocationContext.session().userId())` and + `assertEquals(invocationContext.session().userId(), invocationContext.userId())` + to confirm they are consistent. +Validation: + Similar to `appName()`, the `userId()` method on `InvocationContext` delegates to the + session. This test ensures the session returned by `session()` is the authoritative + source for user identity information, verifying that no data inconsistency exists + between direct access and delegated access patterns. + +--- + +Scenario 7: Session Is Preserved After Modifying Other Mutable Fields of InvocationContext + +Details: + TestName: sessionRemainsUnchangedAfterMutatingOtherFields + Description: Verifies that the `session` field remains unchanged even after mutating + other mutable fields of the `InvocationContext` (such as `endInvocation`, + `branch`, or `agent`). Since `session` is `private final`, it must not + be affected by mutations to other fields. +Execution: + Arrange: Create a mock `Session` object. Build an `InvocationContext` with that session. + Capture the session reference returned by `invocationContext.session()` before + any mutations. + Act: Call `invocationContext.setEndInvocation(true)`, `invocationContext.branch("new-branch")`, + and `invocationContext.agent(mockAgent)`. After these mutations, call + `invocationContext.session()` again. + Assert: Use `assertSame(originalSession, invocationContext.session())` to confirm + the session reference has not changed after mutations. +Validation: + The `session` field is declared as `private final`, meaning it can only be set during + construction and cannot be reassigned. This test provides confidence that mutable + operations on the context do not inadvertently affect the session binding, which is + critical for maintaining session integrity during complex, multi-step agent interactions. + +--- + +Scenario 8: Session Returned by session() Is the Same Object Used in equals() Comparison + +Details: + TestName: sessionContributesToEqualityCheck + Description: Verifies that the `Session` object returned by `session()` is the same + field used in the `equals()` method of `InvocationContext`. Two contexts + built with the same session should reflect equal session references, while + those built with different sessions should not be considered equal solely + based on session difference. +Execution: + Arrange: Create two distinct mock `Session` objects (`sessionA` and `sessionB`). + Build two `InvocationContext` instances using identical builder configurations + except that one uses `sessionA` and the other uses `sessionB`. + Act: Call `session()` on both instances and compare using `assertSame` / `assertNotSame`. + Also call `assertEquals` / `assertNotEquals` on the two context instances. + Assert: Use `assertSame(sessionA, contextA.session())`, `assertSame(sessionB, contextB.session())`, + `assertNotSame(contextA.session(), contextB.session())`, and + `assertNotEquals(contextA, contextB)`. +Validation: + The `equals()` method in `InvocationContext` includes `Objects.equals(session, that.session)` + as part of its equality logic. This test confirms that `session()` returns the field + that participates in equality checks. Verifying this is important for use cases where + contexts are compared, stored in collections, or deduplicated based on their content. + +--- + +Scenario 9: Session Returned via Deprecated Constructor Matches the Provided Session + +Details: + TestName: sessionReturnedFromDeprecatedConstructorMatchesInput + Description: Verifies that when the deprecated public constructor of `InvocationContext` + is used to create an instance, the `session()` method still returns the + same `Session` object that was passed in. This ensures backward + compatibility of the `session()` accessor regardless of how the context + was constructed. +Execution: + Arrange: Create mock instances for `BaseSessionService`, `BaseArtifactService`, + `BaseMemoryService`, `PluginManager`, `LiveRequestQueue`, `BaseAgent`, + `Session`, `Content`, and `RunConfig`. Use the deprecated constructor: + `new InvocationContext(sessionService, artifactService, memoryService, + pluginManager, Optional.of(liveRequestQueue), Optional.empty(), "inv-id", + agent, mockSession, Optional.empty(), runConfig, false)`. + Act: Call `invocationContext.session()` on the instance created via the deprecated + constructor. + Assert: Use `assertSame(mockSession, invocationContext.session())` to confirm + the correct session is returned. +Validation: + Even though the deprecated constructors delegate to the builder internally, this test + ensures the session field is correctly threaded through the builder chain and that + no accidental data loss occurs. It preserves confidence in backward compatibility for + existing users of the deprecated API. + +--- + +Scenario 10: Session Returned via Static create() Factory Method Matches the Provided Session + +Details: + TestName: sessionReturnedFromStaticCreateMethodMatchesInput + Description: Verifies that when the deprecated static `create()` factory method is used + to instantiate `InvocationContext`, the `session()` accessor correctly + returns the `Session` object passed to the factory method. +Execution: + Arrange: Create mock instances for `BaseSessionService`, `BaseArtifactService`, + `BaseAgent`, `Session`, `Content`, and `RunConfig`. Use the deprecated + static factory: `InvocationContext.create(sessionService, artifactService, + "inv-id", agent, mockSession, userContent, runConfig)`. + Act: Call `invocationContext.session()` on the resulting instance. + Assert: Use `assertSame(mockSession, invocationContext.session())` to confirm the + session is the exact object passed to the factory method. +Validation: + The static `create()` methods are deprecated but still part of the public API contract. + This test ensures that regardless of the instantiation mechanism, the `session()` + accessor consistently returns the correct `Session` object, reinforcing that all + creation paths correctly initialize the internal `session` field. + +*/ + +// ********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 InvocationContextSessionTest { + + @Mock + private BaseSessionService mockSessionService; + + @Mock + private BaseArtifactService mockArtifactService; + + @Mock + private BaseMemoryService mockMemoryService; + + @Mock + private PluginManager mockPluginManager; + + @Mock + private Session mockSession; + + @Mock + private Session mockSessionA; + + @Mock + private Session mockSessionB; + + @Mock + private BaseAgent mockAgent; + + @Mock + private Content mockUserContent; + + @Mock + private RunConfig mockRunConfig; + + @Mock + private LiveRequestQueue mockLiveRequestQueue; + + @Mock + private ResumabilityConfig mockResumabilityConfig; + + private static final String INVOCATION_ID = "test-invocation-id"; + + private static final String APP_NAME = "test-app"; + + private static final String USER_ID = "user-123"; + + @BeforeEach + void setUp() { + lenient().when(mockSession.appName()).thenReturn(APP_NAME); + lenient().when(mockSession.userId()).thenReturn(USER_ID); + lenient().when(mockRunConfig.maxLlmCalls()).thenReturn(500); + } + + @Test + @Tag("valid") + void sessionReturnsSessionSetViaBuilder() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.ofNullable(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + // Act + Session result = invocationContext.session(); + // Assert + assertSame(mockSession, result); + } + + @Test + @Tag("valid") + void sessionReturnsNonNullWhenValidSessionProvided() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.ofNullable(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + // Act + Session result = invocationContext.session(); + // Assert + assertNotNull(result); + } + + @Test + @Tag("valid") + void sessionReturnsSameInstanceOnMultipleCalls() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.ofNullable(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + // Act + Session firstResult = invocationContext.session(); + Session secondResult = invocationContext.session(); + Session thirdResult = invocationContext.session(); + // Assert + assertSame(firstResult, secondResult); + assertSame(firstResult, thirdResult); + } + + @Test + @Tag("integration") + void sessionReturnsSameSessionAfterCopyOf() { + // Arrange + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.ofNullable(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + // Act + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + Session copiedSession = copiedContext.session(); + // Assert + assertSame(originalContext.session(), copiedSession); + assertSame(mockSession, copiedSession); + } + + @Test + @Tag("integration") + void sessionReturnedAllowsAccessToAppName() { + // Arrange + when(mockSession.appName()).thenReturn(APP_NAME); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.ofNullable(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + // Act + String appNameFromSession = invocationContext.session().appName(); + String appNameFromContext = invocationContext.appName(); + // Assert + assertEquals(APP_NAME, appNameFromSession); + assertEquals(invocationContext.session().appName(), appNameFromContext); + assertEquals(APP_NAME, appNameFromContext); + } + + @Test + @Tag("integration") + void sessionReturnedAllowsAccessToUserId() { + // Arrange + when(mockSession.userId()).thenReturn(USER_ID); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.ofNullable(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + // Act + String userIdFromSession = invocationContext.session().userId(); + String userIdFromContext = invocationContext.userId(); + // Assert + assertEquals(USER_ID, userIdFromSession); + assertEquals(invocationContext.session().userId(), userIdFromContext); + assertEquals(USER_ID, userIdFromContext); + } + + @Test + @Tag("valid") + void sessionRemainsUnchangedAfterMutatingOtherFields() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.ofNullable(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + Session originalSession = invocationContext.session(); + // Act + invocationContext.setEndInvocation(true); + invocationContext.branch("new-branch"); + invocationContext.agent(mockAgent); + Session sessionAfterMutations = invocationContext.session(); + // Assert + assertSame(originalSession, sessionAfterMutations); + assertSame(mockSession, sessionAfterMutations); + } + + @Test + @Tag("valid") + void sessionContributesToEqualityCheck() { + // Arrange + InvocationContext contextA = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSessionA) + .userContent(Optional.ofNullable(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + InvocationContext contextB = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSessionB) + .userContent(Optional.ofNullable(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + // Act & Assert + assertSame(mockSessionA, contextA.session()); + assertSame(mockSessionB, contextB.session()); + assertNotSame(contextA.session(), contextB.session()); + assertNotEquals(contextA, contextB); + } + + @Test + @Tag("valid") + void sessionReturnedFromDeprecatedConstructorMatchesInput() { + // Arrange + @SuppressWarnings("deprecation") + InvocationContext invocationContext = new InvocationContext(mockSessionService, mockArtifactService, + mockMemoryService, mockPluginManager, Optional.of(mockLiveRequestQueue), Optional.empty(), + INVOCATION_ID, mockAgent, mockSession, Optional.empty(), mockRunConfig, false); + // Act + Session result = invocationContext.session(); + // Assert + assertSame(mockSession, result); + assertNotNull(result); + } + + @Test + @Tag("valid") + void sessionReturnedFromDeprecatedConstructorWithoutPluginManagerMatchesInput() { + // Arrange + @SuppressWarnings("deprecation") + InvocationContext invocationContext = new InvocationContext(mockSessionService, mockArtifactService, + mockMemoryService, Optional.of(mockLiveRequestQueue), Optional.empty(), INVOCATION_ID, mockAgent, + mockSession, Optional.empty(), mockRunConfig, false); + // Act + Session result = invocationContext.session(); + // Assert + assertSame(mockSession, result); + assertNotNull(result); + } + + @Test + @Tag("valid") + void sessionReturnedFromStaticCreateMethodMatchesInput() { + // Arrange + InvocationContext invocationContext = InvocationContext.create(mockSessionService, mockArtifactService, + INVOCATION_ID, mockAgent, mockSession, mockUserContent, mockRunConfig); + // Act + Session result = invocationContext.session(); + // Assert + assertSame(mockSession, result); + assertNotNull(result); + } + + @Test + @Tag("valid") + void sessionReturnedFromStaticCreateMethodWithLiveQueueMatchesInput() { + // Arrange + InvocationContext invocationContext = InvocationContext.create(mockSessionService, mockArtifactService, + mockAgent, mockSession, mockLiveRequestQueue, mockRunConfig); + // Act + Session result = invocationContext.session(); + // Assert + assertSame(mockSession, result); + assertNotNull(result); + } + + @Test + @Tag("boundary") + void sessionReturnedFromStaticCreateMethodWithNullUserContentMatchesInput() { + // Arrange + InvocationContext invocationContext = InvocationContext.create(mockSessionService, mockArtifactService, + INVOCATION_ID, mockAgent, mockSession, null, mockRunConfig); + // Act + Session result = invocationContext.session(); + // Assert + assertSame(mockSession, result); + assertNotNull(result); + assertTrue(invocationContext.userContent().isEmpty()); + } + + @Test + @Tag("boundary") + void sessionReturnedFromStaticCreateMethodWithNullLiveQueueMatchesInput() { + // Arrange + InvocationContext invocationContext = InvocationContext.create(mockSessionService, mockArtifactService, + mockAgent, mockSession, null, mockRunConfig); + // Act + Session result = invocationContext.session(); + // Assert + assertSame(mockSession, result); + assertNotNull(result); + assertTrue(invocationContext.liveRequestQueue().isEmpty()); + } + + @Test + @Tag("valid") + void sessionIsConsistentWithCopyOfAfterMutation() { + // Arrange + InvocationContext originalContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.ofNullable(mockUserContent)) + .runConfig(mockRunConfig) + .build(); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + // Act + originalContext.setEndInvocation(true); + originalContext.branch("modified-branch"); + // Assert + assertSame(mockSession, originalContext.session()); + assertSame(mockSession, copiedContext.session()); + assertSame(originalContext.session(), copiedContext.session()); + } + + @Test + @Tag("integration") + void sessionAccessorIsConsistentWithAppNameDelegation() { + // Arrange + when(mockSession.appName()).thenReturn(APP_NAME); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act + String appNameViaSession = invocationContext.session().appName(); + String appNameDirectly = invocationContext.appName(); + // Assert + assertNotNull(invocationContext.session()); + assertEquals(appNameViaSession, appNameDirectly); + verify(mockSession, atLeastOnce()).appName(); + } + + @Test + @Tag("integration") + void sessionAccessorIsConsistentWithUserIdDelegation() { + // Arrange + when(mockSession.userId()).thenReturn(USER_ID); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act + String userIdViaSession = invocationContext.session().userId(); + String userIdDirectly = invocationContext.userId(); + // Assert + assertNotNull(invocationContext.session()); + assertEquals(userIdViaSession, userIdDirectly); + verify(mockSession, atLeastOnce()).userId(); + } + +@Test + @Tag("valid") + void sessionFieldParticipatesInHashCode() { + // Arrange + InvocationContext context1 = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(INVOCATION_ID) + .agent(mockAgent) + . \ 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..826d7ce53 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextShouldPauseInvocationTest.java @@ -0,0 +1,377 @@ +/* + * 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: Not Resumable — Always Returns False + +Details: + TestName: shouldReturnFalseWhenInvocationIsNotResumable + Description: Verifies that when the invocation is not resumable (i.e., ResumabilityConfig.isResumable() + returns false), the method returns false regardless of the event's content, including + the presence of long-running tool IDs and matching function calls. + +Execution: + Arrange: + - Create a ResumabilityConfig configured so that isResumable() returns false. + - Build an InvocationContext using InvocationContext.builder() with the non-resumable + ResumabilityConfig, a mock/stub Session, BaseSessionService, and BaseArtifactService. + - Create a mock Event that returns a non-empty Optional> from + longRunningToolIds() containing "tool-1". + - Create a FunctionCall with id Optional.of("tool-1") and add it to the Event so that + functionCalls() returns a list containing that FunctionCall. + + Act: + - Call invocationContext.shouldPauseInvocation(event). + + Assert: + - assertFalse(result), confirming the method returns false. + +Validation: + The assertion verifies the first guard clause: if isResumable() is false, the method + short-circuits and returns false without inspecting the event. This is critical business + logic because non-resumable invocations must never be paused, ensuring that agents + configured without resumability support cannot be interrupted mid-execution. + +*/ + +// ********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.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.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.genai.types.Content; +import com.google.genai.types.FunctionCall; +import java.util.List; +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.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import java.util.Map; +import java.util.Objects; +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 Session mockSession; + + @Mock + private Event mockEvent; + + @Mock + private ResumabilityConfig mockResumabilityConfig; + + private static final String TEST_INVOCATION_ID = "e-" + UUID.randomUUID(); + + 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); + } + + private InvocationContext buildInvocationContext(ResumabilityConfig resumabilityConfig) { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId(TEST_INVOCATION_ID) + .session(mockSession) + .userContent(Optional.empty()) + .resumabilityConfig(resumabilityConfig) + .build(); + } + + // ======================== + // Scenario 1: Not Resumable — Always Returns False + // ======================== + @Test + @Tag("invalid") + void shouldReturnFalseWhenInvocationIsNotResumable() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + FunctionCall functionCall = mock(FunctionCall.class); + when(functionCall.id()).thenReturn(Optional.of("tool-1")); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of("tool-1"))); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(functionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "shouldPauseInvocation should return false when invocation is not resumable"); + } + + // ======================== + // Scenario 2: Resumable but No Long-Running Tool IDs — Returns False + // ======================== + @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, "shouldPauseInvocation should 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, "shouldPauseInvocation should return false when longRunningToolIds is absent"); + } + + // ======================== + // Scenario 3: Resumable, Long-Running Tool IDs Present, Function Call Matches — + // Returns True + // ======================== + @Test + @Tag("valid") + void shouldReturnTrueWhenResumableAndFunctionCallMatchesLongRunningToolId() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + String toolId = "tool-abc"; + FunctionCall functionCall = mock(FunctionCall.class); + when(functionCall.id()).thenReturn(Optional.of(toolId)); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of(toolId))); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(functionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertTrue(result, "shouldPauseInvocation should return true when a function call ID matches a long-running tool ID"); + } + + // ======================== + // Scenario 4: Resumable, Long-Running Tool IDs Present, No Matching Function Call — + // Returns False + // ======================== + @Test + @Tag("invalid") + void shouldReturnFalseWhenResumableAndFunctionCallDoesNotMatchLongRunningToolId() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + FunctionCall functionCall = mock(FunctionCall.class); + when(functionCall.id()).thenReturn(Optional.of("tool-other")); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of("tool-expected"))); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(functionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "shouldPauseInvocation should return false when function call IDs do not match any long-running tool ID"); + } + + // ======================== + // Scenario 5: Resumable, Long-Running Tool IDs Present, Function Call Has Empty ID — + // Returns False + // ======================== + @Test + @Tag("boundary") + void shouldReturnFalseWhenResumableAndFunctionCallHasEmptyId() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + FunctionCall functionCall = mock(FunctionCall.class); + when(functionCall.id()).thenReturn(Optional.empty()); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of("tool-1"))); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(functionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "shouldPauseInvocation should return false when function call has no ID"); + } + + // ======================== + // Scenario 6: Resumable, Long-Running Tool IDs Present, No Function Calls — Returns + // False + // ======================== + @Test + @Tag("boundary") + void shouldReturnFalseWhenResumableAndNoFunctionCallsInEvent() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of("tool-1"))); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of()); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "shouldPauseInvocation should return false when there are no function calls in the event"); + } + + // ======================== + // Scenario 7: Resumable, Multiple Function Calls, Only One Matches — Returns True + // ======================== + @Test + @Tag("valid") + void shouldReturnTrueWhenOnlyOneFunctionCallMatchesLongRunningToolId() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + FunctionCall functionCall1 = mock(FunctionCall.class); + when(functionCall1.id()).thenReturn(Optional.of("tool-no-match")); + FunctionCall functionCall2 = mock(FunctionCall.class); + when(functionCall2.id()).thenReturn(Optional.of("tool-match")); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of("tool-match"))); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(functionCall1, functionCall2)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertTrue(result, "shouldPauseInvocation should return true when at least one function call ID matches a long-running tool ID"); + } + + // ======================== + // Scenario 8: Resumable, Multiple Long-Running Tool IDs, All Function Calls Match — + // Returns True + // ======================== + @Test + @Tag("valid") + void shouldReturnTrueWhenMultipleFunctionCallsMatchMultipleLongRunningToolIds() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + FunctionCall functionCall1 = mock(FunctionCall.class); + when(functionCall1.id()).thenReturn(Optional.of("tool-1")); + FunctionCall functionCall2 = mock(FunctionCall.class); + when(functionCall2.id()).thenReturn(Optional.of("tool-2")); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of("tool-1", "tool-2"))); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(functionCall1, functionCall2)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertTrue(result, "shouldPauseInvocation should return true when multiple function calls match multiple long-running tool IDs"); + } + + // ======================== + // Scenario 9: Resumable, Multiple Function Calls, None Match — Returns False + // ======================== + @Test + @Tag("invalid") + void shouldReturnFalseWhenResumableAndMultipleFunctionCallsNoneMatch() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + FunctionCall functionCall1 = mock(FunctionCall.class); + when(functionCall1.id()).thenReturn(Optional.of("tool-a")); + FunctionCall functionCall2 = mock(FunctionCall.class); + when(functionCall2.id()).thenReturn(Optional.of("tool-b")); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of("tool-x", "tool-y"))); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(functionCall1, functionCall2)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "shouldPauseInvocation should return false when no function call IDs match any long-running tool ID"); + } + + // ======================== + // Scenario 10: Not Resumable — Returns False Even With Empty Long-Running Tool IDs + // ======================== + @Test + @Tag("invalid") + void shouldReturnFalseWhenNotResumableAndNoLongRunningToolIds() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(false); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of())); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of()); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertFalse(result, "shouldPauseInvocation should return false when not resumable, regardless of event content"); + } + + // ======================== + // Scenario 11: Boundary — Single Function Call With Exact ID Match in Single-Element + // Set + // ======================== + @Test + @Tag("boundary") + void shouldReturnTrueWhenSingleFunctionCallExactlyMatchesSingleLongRunningToolId() { + // Arrange + when(mockResumabilityConfig.isResumable()).thenReturn(true); + InvocationContext invocationContext = buildInvocationContext(mockResumabilityConfig); + String exactToolId = "exact-tool-id-123"; + FunctionCall functionCall = mock(FunctionCall.class); + when(functionCall.id()).thenReturn(Optional.of(exactToolId)); + when(mockEvent.longRunningToolIds()).thenReturn(Optional.of(ImmutableSet.of(exactToolId))); + when(mockEvent.functionCalls()).thenReturn(ImmutableList.of(functionCall)); + // Act + boolean result = invocationContext.shouldPauseInvocation(mockEvent); + // Assert + assertTrue(result, "shouldPauseInvocation should return true for exact single match between function call ID and long-running tool ID"); + } + +// ======================== +// Scenario 12: Resumable, Function Calls with Mix of Present and Empty IDs +// ======================== +@Test + @Tag("boundary") + void shouldReturnTrueWhenMixedFunctionCallIdsAndOneMatchesLongRunningToolId() \ 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 100644 index 000000000..67d85b5fd --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextUserContentTest.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=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 when an `InvocationContext` is built with a non-null `Content` object + passed via `userContent(Content)`, the `userContent()` method returns an + `Optional` that is present and contains the exact same `Content` instance. + +Execution: + Arrange: + - Create a mock or concrete `Content` object (e.g., `Content content = Content.newBuilder().build()`). + - Build an `InvocationContext` using `InvocationContext.builder()` and call + `.userContent(content)` during construction. + Act: + - Call `invocationContext.userContent()` on the built instance. + Assert: + - Assert that the returned `Optional` is not empty (`assertTrue(result.isPresent())`). + - Assert that `result.get()` equals the `Content` object provided during construction. + +Validation: + This test confirms that the builder correctly stores a non-null `Content` object and + that the `userContent()` accessor faithfully returns it wrapped in a present `Optional`. + This is essential for downstream agent processing logic that depends on having access + to the triggering user message content. + +*/ + +// ********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 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 RunConfig mockRunConfig; + + @BeforeEach + void setUp() { + when(mockSession.appName()).thenReturn("testApp"); + when(mockSession.userId()).thenReturn("testUser"); + } + + @Test + @Tag("valid") + void userContentReturnsPresentOptionalWhenContentIsProvided() { + // Arrange + Content content = Content.newBuilder().setRole("user").build(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(content) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertTrue(result.isPresent(), "userContent() should return a present Optional when Content is provided"); + assertEquals(content, result.get(), "userContent() should return the exact Content object provided"); + } + + @Test + @Tag("valid") + void userContentReturnsPresentOptionalWhenContentIsSetViaOptional() { + // Arrange + Content content = Content.newBuilder().setRole("user").build(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.of(content)) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertTrue(result.isPresent(), + "userContent() should return a present Optional when Content is set via Optional.of()"); + assertEquals(content, result.get(), + "userContent() should return the exact Content object provided via Optional"); + } + + @Test + @Tag("valid") + void userContentReturnsEmptyOptionalWhenNotSet() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertFalse(result.isPresent(), "userContent() should return an empty Optional when not set"); + } + + @Test + @Tag("valid") + void userContentReturnsEmptyOptionalWhenSetWithEmptyOptional() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertFalse(result.isPresent(), "userContent() should return an empty Optional when set with Optional.empty()"); + } + + @Test + @Tag("valid") + void userContentReturnsEmptyOptionalWhenNullContentPassedViaCreate() { + // Arrange + InvocationContext invocationContext = InvocationContext.create(mockSessionService, mockArtifactService, + "test-invocation-id", mockAgent, mockSession, null, mockRunConfig); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertFalse(result.isPresent(), + "userContent() should return empty Optional when null Content is passed via create()"); + } + + @Test + @Tag("valid") + void userContentReturnsPresentOptionalWhenContentPassedViaCreate() { + // Arrange + Content content = Content.newBuilder().setRole("user").build(); + InvocationContext invocationContext = InvocationContext.create(mockSessionService, mockArtifactService, + "test-invocation-id", mockAgent, mockSession, content, mockRunConfig); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertTrue(result.isPresent(), + "userContent() should return a present Optional when Content is passed via create()"); + assertEquals(content, result.get(), "userContent() should return the exact Content passed via create()"); + } + + @Test + @Tag("valid") + void userContentIsPreservedAfterCopyOf() { + // Arrange + Content content = Content.newBuilder().setRole("user").build(); + InvocationContext original = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(content) + .runConfig(mockRunConfig) + .build(); + // Act + InvocationContext copy = InvocationContext.copyOf(original); + Optional result = copy.userContent(); + // Assert + assertTrue(result.isPresent(), "userContent() should be present after copyOf()"); + assertEquals(content, result.get(), "userContent() should preserve the same Content after copyOf()"); + } + + @Test + @Tag("valid") + 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 copy = InvocationContext.copyOf(original); + Optional result = copy.userContent(); + // Assert + assertFalse(result.isPresent(), + "userContent() should remain empty after copyOf() when original had empty content"); + } + + @Test + @Tag("valid") + void userContentReturnsSameReferenceAcrossMultipleCalls() { + // Arrange + Content content = Content.newBuilder().setRole("user").build(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(content) + .runConfig(mockRunConfig) + .build(); + // Act + Optional firstCall = invocationContext.userContent(); + Optional secondCall = invocationContext.userContent(); + // Assert + assertEquals(firstCall, secondCall, "userContent() should return equal values across multiple calls"); + assertTrue(firstCall.isPresent(), "userContent() should be present on first call"); + assertTrue(secondCall.isPresent(), "userContent() should be present on second call"); + assertSame(firstCall.get(), secondCall.get(), + "userContent() should return the same Content instance on repeated calls"); + } + + @Test + @Tag("valid") + void userContentReturnsCorrectContentWithRole() { + // Arrange + Content content = Content.newBuilder().setRole("model").build(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(content) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertTrue(result.isPresent(), "userContent() should be present"); + assertEquals("model", result.get().role().orElse(""), "userContent() should preserve the role of the Content"); + } + + @Test + @Tag("boundary") + void userContentNotNullWhenBuiltWithoutExplicitUserContent() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertNotNull(result, "userContent() should never return null, only Optional.empty()"); + } + + @Test + @Tag("boundary") + void userContentNotNullWhenExplicitlySetToEmptyOptional() { + // Arrange + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertNotNull(result, "userContent() should never return null even when set to Optional.empty()"); + assertFalse(result.isPresent(), "userContent() should return empty Optional when set to Optional.empty()"); + } + + @Test + @Tag("integration") + void userContentIsAccessibleAfterFullBuilderConstruction() { + // Arrange + Content content = Content.newBuilder().setRole("user").build(); + String invocationId = InvocationContext.newInvocationContextId(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .memoryService(mockMemoryService) + .pluginManager(mockPluginManager) + .invocationId(invocationId) + .agent(mockAgent) + .session(mockSession) + .userContent(content) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertNotNull(result, "userContent() should not be null"); + assertTrue(result.isPresent(), "userContent() should be present after full builder construction"); + assertEquals(content, result.get(), "userContent() should match the Content provided during construction"); + } + + @Test + @Tag("integration") + void userContentIsIndependentOfOtherContextProperties() { + // Arrange + Content contentForFirst = Content.newBuilder().setRole("user").build(); + Content contentForSecond = Content.newBuilder().setRole("model").build(); + InvocationContext firstContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("invocation-1") + .agent(mockAgent) + .session(mockSession) + .userContent(contentForFirst) + .runConfig(mockRunConfig) + .build(); + InvocationContext secondContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("invocation-2") + .agent(mockAgent) + .session(mockSession) + .userContent(contentForSecond) + .runConfig(mockRunConfig) + .build(); + // Act + Optional firstResult = firstContext.userContent(); + Optional secondResult = secondContext.userContent(); + // Assert + assertTrue(firstResult.isPresent(), "First context userContent() should be present"); + assertTrue(secondResult.isPresent(), "Second context userContent() should be present"); + assertEquals(contentForFirst, firstResult.get(), "First context userContent() should match its own Content"); + assertEquals(contentForSecond, secondResult.get(), "Second context userContent() should match its own Content"); + assertNotEquals(firstResult.get(), secondResult.get(), + "Two different contexts should have different userContent"); + } + + @Test + @Tag("valid") + void userContentReturnsOptionalTypeCorrectly() { + // Arrange + Content content = Content.newBuilder().setRole("user").build(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(content) + .runConfig(mockRunConfig) + .build(); + // Act + Optional result = invocationContext.userContent(); + // Assert + assertNotNull(result, "userContent() must not return null"); + assertInstanceOf(Optional.class, result, "userContent() must return an Optional type"); + } + +@Test + @Tag("valid") + void userContentIsNotAffectedByChangingOtherFields() { + // Arrange + Content content = Content.newBuilder().setRole("user").build(); + InvocationContext invocationContext = InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(mockSession) + .userContent(content) + .runConfig(mockRunConfig) + .build(); + // Modify other properties on the context + invocationContext.branch("test-branch"); + invocationContext.setEndInvocation(true); \ No newline at end of file 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..8e0b006c2 --- /dev/null +++ b/core/src/test/java/com/google/adk/agents/InvocationContextUserIdTest.java @@ -0,0 +1,604 @@ +/* + * 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 for a Standard Alphanumeric User ID + +Details: + TestName: userIdReturnsCorrectValueFromSession + Description: Verifies that the `userId()` method correctly delegates to `session.userId()` + and returns the exact user ID string stored in the session. This is the + primary happy-path test to confirm the delegation chain works as expected. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return a standard alphanumeric user ID string, + e.g., "user123". + - Build an `InvocationContext` using `InvocationContext.builder()`, providing + the mocked session via `.session(mockSession)`, along with any other minimally + required fields (e.g., a mock `BaseSessionService` and `BaseArtifactService`). + Act: - Call `invocationContext.userId()` on the constructed `InvocationContext` instance. + Assert: - Use `assertEquals("user123", invocationContext.userId())` to verify the result. +Validation: + The assertion confirms that `userId()` faithfully delegates to `session.userId()` and + returns the value unchanged. This is critical because any incorrect mapping or data + transformation would cause identity-related failures throughout the application. + +--- + +Scenario 2: Return User ID When the Session Contains a UUID-Format User ID + +Details: + TestName: userIdReturnsUuidFormattedUserId + Description: Verifies that `userId()` correctly returns a UUID-formatted string when + the session is initialized with a UUID as the user identifier. This + ensures the method handles UUID-format strings without alteration. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return a UUID string, e.g., + "550e8400-e29b-41d4-a716-446655440000". + - Build an `InvocationContext` using the builder with the mocked session. + Act: - Call `invocationContext.userId()`. + Assert: - Use `assertEquals("550e8400-e29b-41d4-a716-446655440000", invocationContext.userId())`. +Validation: + Confirms that `userId()` does not truncate, modify, or reformat UUID-style identifiers. + Many systems use UUID strings as user IDs; correct passthrough is essential for + proper session tracking and user identification. + +--- + +Scenario 3: Return User ID Consistent With the Session's App Name Context + +Details: + TestName: userIdIsConsistentWithSessionAppName + Description: Verifies that `userId()` returns the correct user ID in the context of a + fully built `InvocationContext` that also has an `appName`, ensuring that + the method returns the session-level user ID and not any other contextual + identifier. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return "alice". + - Stub `session.appName()` to return "myApp". + - Build an `InvocationContext` using the builder with the mocked session. + Act: - Call `invocationContext.userId()` and also `invocationContext.appName()` + for cross-verification. + Assert: - Use `assertEquals("alice", invocationContext.userId())`. + - Use `assertEquals("myApp", invocationContext.appName())` as a secondary check. +Validation: + Ensures that `userId()` and `appName()` are independent delegates to the same session + object. Mixing up user IDs and app names would cause significant authorization and + auditing bugs. + +--- + +Scenario 4: Return User ID When Session Is Provided via the Deprecated Constructor + +Details: + TestName: userIdReturnsCorrectValueWhenBuiltWithDeprecatedConstructor + Description: Verifies that `userId()` works correctly when the `InvocationContext` is + instantiated using one of the deprecated constructors that accept a `Session` + parameter directly, ensuring backward compatibility. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return "legacyUser". + - Create an `InvocationContext` using the deprecated constructor: + `new InvocationContext(mockSessionService, mockArtifactService, + mockMemoryService, mockPluginManager, Optional.empty(), Optional.empty(), + "inv-001", mockAgent, mockSession, Optional.empty(), mockRunConfig, false)`. + Act: - Call `invocationContext.userId()`. + Assert: - Use `assertEquals("legacyUser", invocationContext.userId())`. +Validation: + Confirms that the deprecated constructor correctly assigns the session field, and that + `userId()` returns the expected value through the legacy construction path. This is + important for maintaining backward compatibility in systems still using the old API. + +--- + +Scenario 5: Return User ID When InvocationContext Is Created via copyOf + +Details: + TestName: userIdReturnsSameValueAfterCopyOf + Description: Verifies that after performing a shallow copy of an `InvocationContext` + using `InvocationContext.copyOf()`, the `userId()` method on the copied + context returns the same user ID as the original context. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return "originalUser". + - Build an original `InvocationContext` using the builder with the mocked session. + - Create a copy using `InvocationContext.copyOf(originalContext)`. + Act: - Call `copiedContext.userId()`. + Assert: - Use `assertEquals("originalUser", copiedContext.userId())`. + - Optionally use `assertEquals(originalContext.userId(), copiedContext.userId())`. +Validation: + Confirms that the `copyOf` operation preserves the session reference (shallow copy), and + thus `userId()` returns the same value. This is critical when sub-agents or delegated + contexts must operate under the same user identity. + +--- + +Scenario 6: Return User ID With a Special Characters User ID String + +Details: + TestName: userIdReturnsValueContainingSpecialCharacters + Description: Verifies that `userId()` correctly returns a user ID that contains special + characters such as hyphens, underscores, dots, or email-format strings, + confirming that no sanitization or encoding is applied by the method. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return "user.name+tag@example.com". + - Build an `InvocationContext` using the builder with the mocked session. + Act: - Call `invocationContext.userId()`. + Assert: - Use `assertEquals("user.name+tag@example.com", invocationContext.userId())`. +Validation: + Ensures that the method acts as a transparent pass-through and does not alter user ID + strings with special characters. Email-format user IDs are common in enterprise systems, + and any transformation would break downstream user lookup and authorization logic. + +--- + +Scenario 7: Return User ID With a Numeric-Only User ID + +Details: + TestName: userIdReturnsNumericOnlyUserId + Description: Verifies that `userId()` correctly returns a user ID that consists entirely + of numeric characters, ensuring the method treats the user ID as a plain + string without any numeric conversion or modification. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return "1234567890". + - Build an `InvocationContext` using the builder with the mocked session. + Act: - Call `invocationContext.userId()`. + Assert: - Use `assertEquals("1234567890", invocationContext.userId())`. +Validation: + Confirms that numeric user IDs (such as database primary keys or phone numbers used + as identifiers) are returned exactly as stored in the session, with no type coercion + or trimming. + +--- + +Scenario 8: Return User ID With a Single Character User ID + +Details: + TestName: userIdReturnsSingleCharacterUserId + Description: Verifies that `userId()` correctly handles and returns an extremely short + user ID consisting of a single character, confirming behavior at the + minimum boundary of typical string lengths. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return "a". + - Build an `InvocationContext` using the builder with the mocked session. + Act: - Call `invocationContext.userId()`. + Assert: - Use `assertEquals("a", invocationContext.userId())`. +Validation: + Validates boundary behavior with the minimum non-empty string length. The method must + correctly propagate even the shortest user ID without any default fallback or error. + +--- + +Scenario 9: Return User ID With a Very Long User ID String + +Details: + TestName: userIdReturnsVeryLongUserId + Description: Verifies that `userId()` correctly returns a user ID string that is + unusually long (e.g., 500 characters), ensuring there is no truncation + or buffer-related issues in the delegation chain. +Execution: + Arrange: - Create a mock of `Session`. + - Generate a long string of 500 repeated characters, e.g., "a".repeat(500). + - Stub `session.userId()` to return this long string. + - Build an `InvocationContext` using the builder with the mocked session. + Act: - Call `invocationContext.userId()`. + Assert: - Use `assertEquals("a".repeat(500), invocationContext.userId())`. +Validation: + Ensures that `userId()` does not impose any length restriction on the returned value. + Large user ID strings may appear in certain enterprise or testing environments, and + the method must handle them without data loss. + +--- + +Scenario 10: userId Returns Same Reference on Multiple Calls + +Details: + TestName: userIdReturnsSameValueOnMultipleInvocations + Description: Verifies that multiple consecutive calls to `userId()` on the same + `InvocationContext` instance consistently return the same value, + confirming that the method is stateless and idempotent. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return "consistentUser". + - Build an `InvocationContext` using the builder with the mocked session. + Act: - Call `invocationContext.userId()` three times, storing each result. + Assert: - Use `assertEquals("consistentUser", firstResult)`. + - Use `assertEquals("consistentUser", secondResult)`. + - Use `assertEquals("consistentUser", thirdResult)`. + - Use `assertEquals(firstResult, secondResult)` and + `assertEquals(secondResult, thirdResult)` for cross-call consistency. +Validation: + Confirms that `userId()` is idempotent and does not produce side effects. Repeated + access to the user ID should always yield the same value since the session reference + is immutable (final field). This is important for logging, auditing, and retry logic. + +--- + +Scenario 11: userId Is Independent Across Different InvocationContext Instances + +Details: + TestName: userIdIsIndependentAcrossMultipleContextInstances + Description: Verifies that two different `InvocationContext` instances with different + session mocks return their respective user IDs, confirming proper isolation + between contexts. +Execution: + Arrange: - Create two separate mocks of `Session`: mockSession1 and mockSession2. + - Stub `mockSession1.userId()` to return "userA". + - Stub `mockSession2.userId()` to return "userB". + - Build two separate `InvocationContext` instances using the builder, + one with mockSession1 and one with mockSession2. + Act: - Call `context1.userId()` and `context2.userId()`. + Assert: - Use `assertEquals("userA", context1.userId())`. + - Use `assertEquals("userB", context2.userId())`. + - Use `assertNotEquals(context1.userId(), context2.userId())`. +Validation: + Confirms that each `InvocationContext` instance holds a reference to its own session + and that `userId()` returns the user ID from the correct session. This isolation is + critical in multi-user or concurrent processing scenarios where context mixing would + cause serious security and correctness issues. + +--- + +Scenario 12: userId Returns Null When Session userId Returns Null + +Details: + TestName: userIdReturnsNullWhenSessionReturnsNull + Description: Verifies the behavior of `userId()` when the underlying `session.userId()` + returns null. Since the method directly returns the result of + `session.userId()` with no null guard, the return value should be null. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return null. + - Build an `InvocationContext` using the builder with the mocked session. + Act: - Call `invocationContext.userId()`. + Assert: - Use `assertNull(invocationContext.userId())`. +Validation: + Documents the current behavior of the method when the session provides a null user ID. + Since the implementation is `return session.userId()` with no defensive null check, + callers must be aware of this possibility. This test acts as a contract specification + for null propagation behavior, which is important for preventing NullPointerExceptions + in downstream consumers. + +--- + +Scenario 13: userId Returns Empty String When Session userId Returns Empty String + +Details: + TestName: userIdReturnsEmptyStringWhenSessionReturnsEmptyString + Description: Verifies that `userId()` returns an empty string when `session.userId()` + returns an empty string, confirming that the method does not substitute + a default value or throw an exception for empty user IDs. +Execution: + Arrange: - Create a mock of `Session`. + - Stub `session.userId()` to return "". + - Build an `InvocationContext` using the builder with the mocked session. + Act: - Call `invocationContext.userId()`. + Assert: - Use `assertEquals("", invocationContext.userId())`. + - Use `assertTrue(invocationContext.userId().isEmpty())`. +Validation: + Confirms that the method transparently returns an empty string without throwing an + exception or replacing it with a default value. This establishes the boundary behavior + for empty user identifiers, which may occur in anonymous or unauthenticated session + scenarios. + +*/ + +// ********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) +class InvocationContextUserIdTest { + + @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 RunConfig mockRunConfig; + + @BeforeEach + void setUp() { + mockRunConfig = RunConfig.builder().build(); + } + + private InvocationContext buildContext(Session session) { + return InvocationContext.builder() + .sessionService(mockSessionService) + .artifactService(mockArtifactService) + .invocationId("test-invocation-id") + .agent(mockAgent) + .session(session) + .userContent(Optional.empty()) + .runConfig(mockRunConfig) + .build(); + } + + @Test + @Tag("valid") + void userIdReturnsCorrectValueFromSession() { + when(mockSession.userId()).thenReturn("user123"); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals("user123", invocationContext.userId()); + } + + @Test + @Tag("valid") + void userIdReturnsUuidFormattedUserId() { + String uuidUserId = "550e8400-e29b-41d4-a716-446655440000"; + when(mockSession.userId()).thenReturn(uuidUserId); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals("550e8400-e29b-41d4-a716-446655440000", invocationContext.userId()); + } + + @Test + @Tag("valid") + void userIdIsConsistentWithSessionAppName() { + when(mockSession.userId()).thenReturn("alice"); + when(mockSession.appName()).thenReturn("myApp"); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals("alice", invocationContext.userId()); + assertEquals("myApp", invocationContext.appName()); + } + + @Test + @Tag("valid") + @SuppressWarnings("deprecation") + void userIdReturnsCorrectValueWhenBuiltWithDeprecatedConstructor() { + when(mockSession.userId()).thenReturn("legacyUser"); + InvocationContext invocationContext = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + mockPluginManager, + Optional.empty(), + Optional.empty(), + "inv-001", + mockAgent, + mockSession, + Optional.empty(), + mockRunConfig, + false); + assertEquals("legacyUser", invocationContext.userId()); + } + + @Test + @Tag("valid") + void userIdReturnsSameValueAfterCopyOf() { + when(mockSession.userId()).thenReturn("originalUser"); + InvocationContext originalContext = buildContext(mockSession); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + assertEquals("originalUser", copiedContext.userId()); + assertEquals(originalContext.userId(), copiedContext.userId()); + } + + @Test + @Tag("valid") + void userIdReturnsValueContainingSpecialCharacters() { + String specialUserId = "user.name+tag@example.com"; + when(mockSession.userId()).thenReturn(specialUserId); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals("user.name+tag@example.com", invocationContext.userId()); + } + + @Test + @Tag("valid") + void userIdReturnsNumericOnlyUserId() { + when(mockSession.userId()).thenReturn("1234567890"); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals("1234567890", invocationContext.userId()); + } + + @Test + @Tag("boundary") + void userIdReturnsSingleCharacterUserId() { + when(mockSession.userId()).thenReturn("a"); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals("a", invocationContext.userId()); + } + + @Test + @Tag("boundary") + void userIdReturnsVeryLongUserId() { + String longUserId = "a".repeat(500); + when(mockSession.userId()).thenReturn(longUserId); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals("a".repeat(500), invocationContext.userId()); + assertEquals(500, invocationContext.userId().length()); + } + + @Test + @Tag("valid") + void userIdReturnsSameValueOnMultipleInvocations() { + when(mockSession.userId()).thenReturn("consistentUser"); + InvocationContext invocationContext = buildContext(mockSession); + String firstResult = invocationContext.userId(); + String secondResult = invocationContext.userId(); + String thirdResult = invocationContext.userId(); + assertEquals("consistentUser", firstResult); + assertEquals("consistentUser", secondResult); + assertEquals("consistentUser", thirdResult); + assertEquals(firstResult, secondResult); + assertEquals(secondResult, thirdResult); + } + + @Test + @Tag("valid") + void userIdIsIndependentAcrossMultipleContextInstances() { + Session mockSession1 = mock(Session.class); + Session mockSession2 = mock(Session.class); + when(mockSession1.userId()).thenReturn("userA"); + when(mockSession2.userId()).thenReturn("userB"); + InvocationContext context1 = buildContext(mockSession1); + InvocationContext context2 = buildContext(mockSession2); + assertEquals("userA", context1.userId()); + assertEquals("userB", context2.userId()); + assertNotEquals(context1.userId(), context2.userId()); + } + + @Test + @Tag("invalid") + void userIdReturnsNullWhenSessionReturnsNull() { + when(mockSession.userId()).thenReturn(null); + InvocationContext invocationContext = buildContext(mockSession); + assertNull(invocationContext.userId()); + } + + @Test + @Tag("boundary") + void userIdReturnsEmptyStringWhenSessionReturnsEmptyString() { + when(mockSession.userId()).thenReturn(""); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals("", invocationContext.userId()); + assertTrue(invocationContext.userId().isEmpty()); + } + + @Test + @Tag("integration") + void userIdDelegatesExactlyToSessionUserId() { + when(mockSession.userId()).thenReturn("delegationUser"); + InvocationContext invocationContext = buildContext(mockSession); + String result = invocationContext.userId(); + verify(mockSession, times(1)).userId(); + assertEquals("delegationUser", result); + } + + @Test + @Tag("integration") + void userIdWorksCorrectlyWithCreateFactoryMethod() { + when(mockSession.userId()).thenReturn("factoryUser"); + Content userContent = null; + InvocationContext invocationContext = + InvocationContext.create( + mockSessionService, + mockArtifactService, + "factory-invocation-id", + mockAgent, + mockSession, + userContent, + mockRunConfig); + assertEquals("factoryUser", invocationContext.userId()); + } + + @Test + @Tag("valid") + void userIdReturnsWhitespaceUserIdWithoutModification() { + String whitespaceUserId = " user with spaces "; + when(mockSession.userId()).thenReturn(whitespaceUserId); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals(" user with spaces ", invocationContext.userId()); + } + + @Test + @Tag("valid") + void userIdReturnsUserIdWithUnicodeCharacters() { + String unicodeUserId = "用户_123"; + when(mockSession.userId()).thenReturn(unicodeUserId); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals("用户_123", invocationContext.userId()); + assertNotNull(invocationContext.userId()); + } + + @Test + @Tag("integration") + void userIdConsistentAfterCopyOfWithBranchModification() { + when(mockSession.userId()).thenReturn("branchUser"); + InvocationContext originalContext = buildContext(mockSession); + InvocationContext copiedContext = InvocationContext.copyOf(originalContext); + copiedContext.branch("agentA.agentB"); + assertEquals("branchUser", originalContext.userId()); + assertEquals("branchUser", copiedContext.userId()); + assertEquals(originalContext.userId(), copiedContext.userId()); + } + + @Test + @Tag("valid") + @SuppressWarnings("deprecation") + void userIdReturnsCorrectValueWithDeprecatedThreeParamConstructor() { + when(mockSession.userId()).thenReturn("legacyUser2"); + InvocationContext invocationContext = + new InvocationContext( + mockSessionService, + mockArtifactService, + mockMemoryService, + Optional.empty(), + Optional.empty(), + "inv-002", + mockAgent, + mockSession, + Optional.empty(), + mockRunConfig, + false); + assertEquals("legacyUser2", invocationContext.userId()); + } + + @Test + @Tag("boundary") + void userIdReturnsExactly255CharacterUserId() { + String userId255 = "u".repeat(255); + when(mockSession.userId()).thenReturn(userId255); + InvocationContext invocationContext = buildContext(mockSession); + assertEquals(userId255, invocationContext.userId()); + assertEquals(255, invocationContext.userId().length()); + } + +} diff --git a/pom.xml b/pom.xml index 6009c7316..24fd6ece6 100644 --- 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