From 451cf3fea8fce8c068df2a519110616bac3618ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20K=C3=A1konyi?= Date: Mon, 22 Dec 2025 14:44:40 +0100 Subject: [PATCH 1/3] fix: guard missing sessions list Prevent NPE when Vertex AI listSessions response omits or nulls the `sessions` field. Adds coverage for missing and null `sessions` responses. --- .../adk/sessions/VertexAiSessionService.java | 13 ++++-- .../sessions/VertexAiSessionServiceTest.java | 40 +++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java b/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java index 7878daf22..a10ce03fb 100644 --- a/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java +++ b/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java @@ -119,15 +119,20 @@ public Single listSessions(String appName, String userId) .map( listSessionsResponseMap -> parseListSessionsResponse(listSessionsResponseMap, appName, userId)) - .defaultIfEmpty(ListSessionsResponse.builder().build()); + .defaultIfEmpty(ListSessionsResponse.builder().sessions(new ArrayList<>()).build()); } private ListSessionsResponse parseListSessionsResponse( JsonNode listSessionsResponseMap, String appName, String userId) { + JsonNode sessionsNode = listSessionsResponseMap.get("sessions"); + if (sessionsNode == null || sessionsNode.isNull() || sessionsNode.isEmpty()) { + return ListSessionsResponse.builder().sessions(new ArrayList<>()).build(); + } List> apiSessions = - objectMapper.convertValue( - listSessionsResponseMap.get("sessions"), - new TypeReference>>() {}); + objectMapper.convertValue(sessionsNode, new TypeReference>>() {}); + if (apiSessions == null) { + apiSessions = new ArrayList<>(); + } List sessions = new ArrayList<>(); for (Map apiSession : apiSessions) { diff --git a/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java b/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java index 775b465ff..e99862654 100644 --- a/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java +++ b/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java @@ -25,6 +25,8 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import okhttp3.MediaType; +import okhttp3.ResponseBody; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -37,6 +39,8 @@ public class VertexAiSessionServiceTest { private static final ObjectMapper mapper = JsonBaseModel.getMapper(); + private static final MediaType JSON_MEDIA_TYPE = + MediaType.parse("application/json; charset=utf-8"); private static final String MOCK_SESSION_STRING_1 = """ @@ -322,6 +326,42 @@ public void listSessions_empty() { .isEmpty(); } + @Test + public void listSessions_missingSessionsField_returnsEmpty() { + when(mockApiClient.request("GET", "reasoningEngines/123/sessions?filter=user_id=userX", "")) + .thenReturn( + new ApiResponse() { + @Override + public ResponseBody getResponseBody() { + return ResponseBody.create(JSON_MEDIA_TYPE, "{}"); + } + + @Override + public void close() {} + }); + + assertThat(vertexAiSessionService.listSessions("123", "userX").blockingGet().sessions()) + .isEmpty(); + } + + @Test + public void listSessions_nullSessionsField_returnsEmpty() { + when(mockApiClient.request("GET", "reasoningEngines/123/sessions?filter=user_id=userY", "")) + .thenReturn( + new ApiResponse() { + @Override + public ResponseBody getResponseBody() { + return ResponseBody.create(JSON_MEDIA_TYPE, "{\"sessions\": null}"); + } + + @Override + public void close() {} + }); + + assertThat(vertexAiSessionService.listSessions("123", "userY").blockingGet().sessions()) + .isEmpty(); + } + @Test public void listEvents_empty() { assertThat(vertexAiSessionService.listEvents("789", "user1", "3").blockingGet().events()) From d0cd877ba1d171e1fbe26bda41c5bd108b992c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20K=C3=A1konyi?= Date: Mon, 22 Dec 2025 14:59:30 +0100 Subject: [PATCH 2/3] Remove redundant apiSessions null check --- .../java/com/google/adk/sessions/VertexAiSessionService.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java b/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java index a10ce03fb..a109f32be 100644 --- a/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java +++ b/core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java @@ -130,9 +130,6 @@ private ListSessionsResponse parseListSessionsResponse( } List> apiSessions = objectMapper.convertValue(sessionsNode, new TypeReference>>() {}); - if (apiSessions == null) { - apiSessions = new ArrayList<>(); - } List sessions = new ArrayList<>(); for (Map apiSession : apiSessions) { From 89b3ed77c32c99be3da4378ca087ecf0091cf93f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20K=C3=A1konyi?= Date: Mon, 22 Dec 2025 15:00:46 +0100 Subject: [PATCH 3/3] Deduplicate ApiResponse test setup --- .../sessions/VertexAiSessionServiceTest.java | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java b/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java index e99862654..b8af31133 100644 --- a/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java +++ b/core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java @@ -42,6 +42,18 @@ public class VertexAiSessionServiceTest { private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8"); + private static ApiResponse apiResponseJson(String json) { + return new ApiResponse() { + @Override + public ResponseBody getResponseBody() { + return ResponseBody.create(JSON_MEDIA_TYPE, json); + } + + @Override + public void close() {} + }; + } + private static final String MOCK_SESSION_STRING_1 = """ { @@ -329,16 +341,7 @@ public void listSessions_empty() { @Test public void listSessions_missingSessionsField_returnsEmpty() { when(mockApiClient.request("GET", "reasoningEngines/123/sessions?filter=user_id=userX", "")) - .thenReturn( - new ApiResponse() { - @Override - public ResponseBody getResponseBody() { - return ResponseBody.create(JSON_MEDIA_TYPE, "{}"); - } - - @Override - public void close() {} - }); + .thenReturn(apiResponseJson("{}")); assertThat(vertexAiSessionService.listSessions("123", "userX").blockingGet().sessions()) .isEmpty(); @@ -347,16 +350,7 @@ public void close() {} @Test public void listSessions_nullSessionsField_returnsEmpty() { when(mockApiClient.request("GET", "reasoningEngines/123/sessions?filter=user_id=userY", "")) - .thenReturn( - new ApiResponse() { - @Override - public ResponseBody getResponseBody() { - return ResponseBody.create(JSON_MEDIA_TYPE, "{\"sessions\": null}"); - } - - @Override - public void close() {} - }); + .thenReturn(apiResponseJson("{\"sessions\": null}")); assertThat(vertexAiSessionService.listSessions("123", "userY").blockingGet().sessions()) .isEmpty();