Skip to content

Commit 8bd0ed9

Browse files
committed
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.
1 parent f6ff2f2 commit 8bd0ed9

File tree

2 files changed

+49
-4
lines changed

2 files changed

+49
-4
lines changed

core/src/main/java/com/google/adk/sessions/VertexAiSessionService.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,20 @@ public Single<ListSessionsResponse> listSessions(String appName, String userId)
119119
.map(
120120
listSessionsResponseMap ->
121121
parseListSessionsResponse(listSessionsResponseMap, appName, userId))
122-
.defaultIfEmpty(ListSessionsResponse.builder().build());
122+
.defaultIfEmpty(ListSessionsResponse.builder().sessions(new ArrayList<>()).build());
123123
}
124124

125125
private ListSessionsResponse parseListSessionsResponse(
126126
JsonNode listSessionsResponseMap, String appName, String userId) {
127+
JsonNode sessionsNode = listSessionsResponseMap.get("sessions");
128+
if (sessionsNode == null || sessionsNode.isNull() || sessionsNode.isEmpty()) {
129+
return ListSessionsResponse.builder().sessions(new ArrayList<>()).build();
130+
}
127131
List<Map<String, Object>> apiSessions =
128-
objectMapper.convertValue(
129-
listSessionsResponseMap.get("sessions"),
130-
new TypeReference<List<Map<String, Object>>>() {});
132+
objectMapper.convertValue(sessionsNode, new TypeReference<List<Map<String, Object>>>() {});
133+
if (apiSessions == null) {
134+
apiSessions = new ArrayList<>();
135+
}
131136

132137
List<Session> sessions = new ArrayList<>();
133138
for (Map<String, Object> apiSession : apiSessions) {

core/src/test/java/com/google/adk/sessions/VertexAiSessionServiceTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import java.util.Optional;
2626
import java.util.concurrent.ConcurrentHashMap;
2727
import java.util.concurrent.ConcurrentMap;
28+
import okhttp3.MediaType;
29+
import okhttp3.ResponseBody;
2830
import org.junit.Before;
2931
import org.junit.Test;
3032
import org.junit.runner.RunWith;
@@ -37,6 +39,8 @@
3739
public class VertexAiSessionServiceTest {
3840

3941
private static final ObjectMapper mapper = JsonBaseModel.getMapper();
42+
private static final MediaType JSON_MEDIA_TYPE =
43+
MediaType.parse("application/json; charset=utf-8");
4044

4145
private static final String MOCK_SESSION_STRING_1 =
4246
"""
@@ -322,6 +326,42 @@ public void listSessions_empty() {
322326
.isEmpty();
323327
}
324328

329+
@Test
330+
public void listSessions_missingSessionsField_returnsEmpty() {
331+
when(mockApiClient.request("GET", "reasoningEngines/123/sessions?filter=user_id=userX", ""))
332+
.thenReturn(
333+
new ApiResponse() {
334+
@Override
335+
public ResponseBody getResponseBody() {
336+
return ResponseBody.create(JSON_MEDIA_TYPE, "{}");
337+
}
338+
339+
@Override
340+
public void close() {}
341+
});
342+
343+
assertThat(vertexAiSessionService.listSessions("123", "userX").blockingGet().sessions())
344+
.isEmpty();
345+
}
346+
347+
@Test
348+
public void listSessions_nullSessionsField_returnsEmpty() {
349+
when(mockApiClient.request("GET", "reasoningEngines/123/sessions?filter=user_id=userY", ""))
350+
.thenReturn(
351+
new ApiResponse() {
352+
@Override
353+
public ResponseBody getResponseBody() {
354+
return ResponseBody.create(JSON_MEDIA_TYPE, "{\"sessions\": null}");
355+
}
356+
357+
@Override
358+
public void close() {}
359+
});
360+
361+
assertThat(vertexAiSessionService.listSessions("123", "userY").blockingGet().sessions())
362+
.isEmpty();
363+
}
364+
325365
@Test
326366
public void listEvents_empty() {
327367
assertThat(vertexAiSessionService.listEvents("789", "user1", "3").blockingGet().events())

0 commit comments

Comments
 (0)