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/memory/AutoValueMemoryEntryContentTest.java b/core/src/test/java/com/google/adk/memory/AutoValueMemoryEntryContentTest.java new file mode 100644 index 000000000..d33cd2844 --- /dev/null +++ b/core/src/test/java/com/google/adk/memory/AutoValueMemoryEntryContentTest.java @@ -0,0 +1,129 @@ +// ********RoostGPT******** +/* +Test generated by RoostGPT for test unit-java-adk using AI Type Azure Open AI and AI Model gpt-5 + +ROOST_METHOD_HASH=content_f95ec3ddc9 +ROOST_METHOD_SIG_HASH=content_accc65ce1a + + +Scenario 1: Returns the same Content instance that was supplied at construction + +Details: + TestName: returnsSameContentInstance + Description: Verify that the content() method returns the exact Content object that was used to build the AutoValue_MemoryEntry instance (reference identity, not a copy). + +Execution: + Arrange: Create a valid Content instance (e.g., a concrete instance or a simple test double) and build a MemoryEntry via the Builder with that Content. + Act: Call content() on the built MemoryEntry. + Assert: Use JUnit assertions to confirm that the returned Content is the same object instance as the one provided during construction (reference equality). + +Validation: + This assertion verifies that content() is a simple accessor returning the internal field without copying or transforming it. It ensures the method exposes the exact Content reference stored in the entry, which is important for consumers relying on object identity or avoiding unnecessary allocations. + +*/ + +// ********RoostGPT******** +package com.google.adk.memory; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.genai.types.Content; +import javax.annotation.Nullable; +import javax.annotation.processing.Generated; +import org.junit.jupiter.api.*; +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; + +@Generated("com.google.auto.value.processor.AutoValueProcessor") +@ExtendWith(MockitoExtension.class) +public class AutoValueMemoryEntryContentTest { + + @Mock private Content contentA; + + @Mock private Content contentB; + + private static final class DummyAnnotated { + + @JsonProperty("content") + @Nullable + String v; + } + + @Test + @Tag("valid") + public void testReturnsSameContentInstance() { + MemoryEntry.Builder builder = MemoryEntry.builder(); + MemoryEntry entry = + builder + .content(contentA) + .author("Alice") // TODO: Change test author if necessary + .timestamp("2023-01-01T00:00:00Z") // TODO: Provide a valid ISO 8601 timestamp + // if necessary + .build(); + Content actual = entry.content(); + assertSame((Content) contentA, (Content) actual); + } + + @Test + @Tag("invalid") + public void testMissingContentThrowsException() { + MemoryEntry.Builder builder = + MemoryEntry.builder().author("Bob").timestamp("2023-01-01T00:00:00Z"); + assertThrows(IllegalStateException.class, builder::build); + } + + @Test + @Tag("boundary") + public void testAllowsNullAuthorAndTimestamp() { + MemoryEntry.Builder builder = MemoryEntry.builder(); + MemoryEntry entry = + builder + .content(contentA) + .author((String) null) // Disambiguate potential overloaded method call with + // null + .timestamp((String) null) // Disambiguate potential overloaded method call + // with null + .build(); + assertSame((Content) contentA, (Content) entry.content()); + assertNull((String) entry.author()); + assertNull((String) entry.timestamp()); + } + + @Test + @Tag("integration") + public void testToBuilderRetainsSameContentInstance() { + MemoryEntry entry = + MemoryEntry.builder() + .content(contentA) + .author("Carol") + .timestamp("2023-01-01T00:00:00Z") + .build(); + MemoryEntry.Builder copyBuilder = entry.toBuilder(); + MemoryEntry copied = copyBuilder.build(); + assertSame((Content) entry.content(), (Content) copied.content()); + assertEquals((String) entry.author(), (String) copied.author()); + assertEquals((String) entry.timestamp(), (String) copied.timestamp()); + } + + @Test + @Tag("valid") + public void testDifferentContentInstancesRemainDistinct() { + MemoryEntry first = + MemoryEntry.builder() + .content(contentA) + .author("Dave") + .timestamp("2023-01-01T00:00:00Z") + .build(); + MemoryEntry second = + MemoryEntry.builder() + .content(contentB) + .author("Eve") + .timestamp("2023-01-02T00:00:00Z") + .build(); + assertNotSame((Content) first.content(), (Content) second.content()); + } +} diff --git a/core/src/test/java/com/google/adk/memory/InMemoryMemoryServiceAddSessionToMemoryTest.java b/core/src/test/java/com/google/adk/memory/InMemoryMemoryServiceAddSessionToMemoryTest.java new file mode 100644 index 000000000..0127d2d9a --- /dev/null +++ b/core/src/test/java/com/google/adk/memory/InMemoryMemoryServiceAddSessionToMemoryTest.java @@ -0,0 +1,229 @@ +/* + * 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 unit-java-adk using AI Type Azure Open AI and AI Model gpt-5 + +ROOST_METHOD_HASH=addSessionToMemory_8a630adf5c +ROOST_METHOD_SIG_HASH=addSessionToMemory_3e3003d0a0 + +Scenario 1: Successfully stores a session with events that have non-empty parts + +Details: + TestName: storesSessionWithEventsHavingNonEmptyParts + Description: Verifies that addSessionToMemory stores a session whose events include content with present, non-empty parts. Confirms that the returned Completable completes without errors and that the stored events are discoverable via searchMemory. + +Execution: + Arrange: Create a Session with appName "appA", userId "user1", session id "s1", and events where at least one event has content().isPresent() and parts().isPresent() with a non-empty list containing a Part with non-empty text like "hello world". + Act: Invoke addSessionToMemory(session) and await the Completable to complete. Then call searchMemory("appA", "user1", "hello") to retrieve memories. + Assert: Assert the Completable completes successfully. Assert that searchMemory returns at least one MemoryEntry with content containing the word "hello". + +Validation: + Confirms that events meeting the filter (content present, parts present and non-empty) are retained and can be retrieved through searchMemory. Ensures the Completable signals completion, indicating the write to memory occurred. + +*/ + +// ********RoostGPT******** + +package com.google.adk.memory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +import com.google.adk.events.Event; +import com.google.adk.sessions.Session; +import com.google.genai.types.Content; +import com.google.genai.types.Part; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Single; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InMemoryMemoryServiceAddSessionToMemoryTest { + + private InMemoryMemoryService service; + + @BeforeEach + public void setUp() { + service = new InMemoryMemoryService(); + } + + @Test + @Tag("integration") + public void storesSessionWithEventsHavingNonEmptyParts() { + String appName = "appA"; + String userId = "user1"; + String sessionId = "s1"; + Event event = mockEventWithTextParts("hello world", 1_700_000_000L); + Session session = mockSession(appName, userId, sessionId, List.of(event)); + Completable completable = service.addSessionToMemory(session); + completable.test().assertComplete().assertNoErrors(); + Single single = service.searchMemory(appName, userId, "hello"); + SearchMemoryResponse resp = single.blockingGet(); + assertNotNull(resp, "Search response should not be null"); + List memories = new ArrayList<>(resp.memories()); + assertTrue((int) memories.size() >= 1, "Expected at least one memory"); + boolean containsHello = + memories.stream() + .flatMap(m -> m.content().parts().orElse(Collections.emptyList()).stream()) + .map(p -> p.text().orElse("")) + .anyMatch(t -> t.toLowerCase().contains("hello")); + assertTrue(containsHello, "Expected at least one memory to contain 'hello'"); + } + + @Test + @Tag("boundary") + public void addIgnoresEventsWithEmptyOrMissingParts() { + String appName = "appB"; + String userId = "userEmpty"; + String sessionId = "sEmpty"; + Event eventWithEmptyParts = mockEventWithEmptyParts(1_700_000_100L); + Session session = mockSession(appName, userId, sessionId, List.of(eventWithEmptyParts)); + Completable completable = service.addSessionToMemory(session); + completable.test().assertComplete().assertNoErrors(); + SearchMemoryResponse resp = service.searchMemory(appName, userId, "anything").blockingGet(); + assertNotNull(resp, "Search response should not be null"); + assertEquals(0, (int) resp.memories().size(), "No memories should be stored for empty parts"); + } + + @Test + @Tag("valid") + public void addStoresSessionWhenMixedEventsOnlyNonEmptyRetained() { + String appName = "appC"; + String userId = "userMix"; + String sessionId = "sMix"; + Event emptyEvent = mockEventWithEmptyParts(1_700_000_200L); + Event nonEmptyEvent = mockEventWithTextParts("This is a test message", 1_700_000_201L); + Session session = mockSession(appName, userId, sessionId, List.of(emptyEvent, nonEmptyEvent)); + Completable completable = service.addSessionToMemory(session); + completable.test().assertComplete().assertNoErrors(); + SearchMemoryResponse resp = service.searchMemory(appName, userId, "test").blockingGet(); + assertNotNull(resp, "Search response should not be null"); + List memories = new ArrayList<>(resp.memories()); + assertTrue((int) memories.size() >= 1, "Expected at least one memory for 'test'"); + boolean containsTest = + memories.stream() + .flatMap(m -> m.content().parts().orElse(Collections.emptyList()).stream()) + .map(p -> p.text().orElse("")) + .anyMatch(t -> t.toLowerCase().contains("test")); + assertTrue(containsTest, "Expected memory content to contain 'test'"); + } + + @Test + @Tag("integration") + public void addStoresOnlyForMatchingAppNameAndUserId() { + String appName1 = "appD"; + String userId1 = "userD1"; + String sessionId1 = "sD1"; + Event event1 = mockEventWithTextParts("alpha beta", 1_700_000_300L); + Session session1 = mockSession(appName1, userId1, sessionId1, List.of(event1)); + String appName2 = "appD"; + String userId2 = "userD2"; + String sessionId2 = "sD2"; + Event event2 = mockEventWithTextParts("gamma delta", 1_700_000_301L); + Session session2 = mockSession(appName2, userId2, sessionId2, List.of(event2)); + service.addSessionToMemory(session1).test().assertComplete().assertNoErrors(); + service.addSessionToMemory(session2).test().assertComplete().assertNoErrors(); + SearchMemoryResponse respUser1 = service.searchMemory(appName1, userId1, "alpha").blockingGet(); + assertNotNull(respUser1, "Search response should not be null"); + assertTrue((int) respUser1.memories().size() >= 1, "User1 should find memory for 'alpha'"); + SearchMemoryResponse respUser2NoAlpha = + service.searchMemory(appName2, userId2, "alpha").blockingGet(); + assertNotNull(respUser2NoAlpha, "Search response should not be null"); + assertEquals( + 0, (int) respUser2NoAlpha.memories().size(), "User2 should not find memory for 'alpha'"); + SearchMemoryResponse respUser2 = service.searchMemory(appName2, userId2, "gamma").blockingGet(); + assertNotNull(respUser2, "Search response should not be null"); + assertTrue((int) respUser2.memories().size() >= 1, "User2 should find memory for 'gamma'"); + SearchMemoryResponse respUser1NoGamma = + service.searchMemory(appName1, userId1, "gamma").blockingGet(); + assertNotNull(respUser1NoGamma, "Search response should not be null"); + assertEquals( + 0, (int) respUser1NoGamma.memories().size(), "User1 should not find memory for 'gamma'"); + } + + @Test + @Tag("invalid") + public void addFailsWhenSessionEventsIsNull() { + String appName = "appE"; + String userId = "userE"; + String sessionId = "sE"; + Session session = Mockito.mock(Session.class); + when(session.appName()).thenReturn(appName); + when(session.userId()).thenReturn(userId); + when(session.id()).thenReturn(sessionId); + when(session.events()).thenReturn(null); + Completable completable = service.addSessionToMemory(session); + completable.test().assertError(NullPointerException.class); + } + + @Test + @Tag("boundary") + public void addWithNoEventsCompletesAndSearchReturnsEmpty() { + String appName = "appF"; + String userId = "userF"; + String sessionId = "sF"; + Session session = mockSession(appName, userId, sessionId, Collections.emptyList()); + Completable completable = service.addSessionToMemory(session); + completable.test().assertComplete().assertNoErrors(); + SearchMemoryResponse resp = service.searchMemory(appName, userId, "any").blockingGet(); + assertNotNull(resp, "Search response should not be null"); + assertEquals(0, (int) resp.memories().size(), "Expected no memories for empty events"); + } + + private Session mockSession(String appName, String userId, String sessionId, List events) { + Session session = Mockito.mock(Session.class); + when(session.appName()).thenReturn(appName); + when(session.userId()).thenReturn(userId); + when(session.id()).thenReturn(sessionId); + when(session.events()).thenReturn(events); + return session; + } + + private Event mockEventWithTextParts(String text, long epochSeconds) { + Event event = Mockito.mock(Event.class); + Content content = Mockito.mock(Content.class); + Part part = Mockito.mock(Part.class); + when(part.text()).thenReturn(Optional.ofNullable(text)); + when(content.parts()).thenReturn(Optional.of(List.of(part))); + when(event.content()).thenReturn(Optional.of(content)); + when(event.author()).thenReturn("author-1"); // TODO: adjust author if needed + when(event.timestamp()).thenReturn(epochSeconds); + return event; + } + + private Event mockEventWithEmptyParts(long epochSeconds) { + Event event = Mockito.mock(Event.class); + Content content = Mockito.mock(Content.class); + when(content.parts()).thenReturn(Optional.of(Collections.emptyList())); + when(event.content()).thenReturn(Optional.of(content)); + when(event.author()).thenReturn("author-empty"); + when(event.timestamp()).thenReturn(epochSeconds); + return event; + } +} 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