@@ -379,4 +379,115 @@ public void appendEvent_withStateRemoved_updatesSessionState() {
379379 assertThat (updatedSession .state ()).containsExactly ("key1" , "value1" );
380380 assertThat (updatedSession .state ()).doesNotContainKey ("key2" );
381381 }
382+
383+ @ Test
384+ public void getSession_eventTimestampAfterUpdateTime_doesNotDropEvent () {
385+ // Regression test: event timestamps are assigned client-side while the
386+ // session updateTime is assigned server-side, so clock skew can make the
387+ // latest event newer than updateTime. Such events must not be dropped by
388+ // getSession().
389+ sessionMap .put ("5" , mockSessionJson ("5" , "2024-12-12T12:12:12.000000Z" ));
390+ eventMap .put (
391+ "5" ,
392+ mockEventsJson (
393+ mockEventJson ("before" , "2024-12-12T12:12:11.000000Z" ),
394+ mockEventJson ("after" , "2024-12-12T12:12:12.500000Z" )));
395+
396+ Session session =
397+ vertexAiSessionService .getSession ("123" , "user" , "5" , Optional .empty ()).blockingGet ();
398+
399+ assertThat (session .events ().stream ().map (Event ::id ))
400+ .containsExactly ("before" , "after" )
401+ .inOrder ();
402+ }
403+
404+ @ Test
405+ public void getSession_afterTimestampConfig_keepsEventsAtOrAfterThreshold () {
406+ sessionMap .put ("6" , mockSessionJson ("6" , "2024-12-12T12:00:30.000000Z" ));
407+ eventMap .put (
408+ "6" ,
409+ mockEventsJson (
410+ mockEventJson ("e1" , "2024-12-12T12:00:05.000000Z" ),
411+ mockEventJson ("e2" , "2024-12-12T12:00:10.000000Z" ),
412+ mockEventJson ("e3" , "2024-12-12T12:00:15.000000Z" )));
413+ GetSessionConfig config =
414+ GetSessionConfig .builder ()
415+ .afterTimestamp (Instant .parse ("2024-12-12T12:00:10.000000Z" ))
416+ .build ();
417+
418+ Session session =
419+ vertexAiSessionService .getSession ("123" , "user" , "6" , Optional .of (config )).blockingGet ();
420+
421+ // The threshold is inclusive: e2 (== afterTimestamp) and e3 are kept, e1 is
422+ // dropped.
423+ assertThat (session .events ().stream ().map (Event ::id )).containsExactly ("e2" , "e3" ).inOrder ();
424+ }
425+
426+ @ Test
427+ public void getSession_afterTimestampBetweenEvents_dropsEventsBeforeThreshold () {
428+ sessionMap .put ("8" , mockSessionJson ("8" , "2024-12-12T12:00:30.000000Z" ));
429+ eventMap .put (
430+ "8" ,
431+ mockEventsJson (
432+ mockEventJson ("e1" , "2024-12-12T12:00:05.000000Z" ),
433+ mockEventJson ("e2" , "2024-12-12T12:00:10.000000Z" ),
434+ mockEventJson ("e3" , "2024-12-12T12:00:15.000000Z" )));
435+ GetSessionConfig config =
436+ GetSessionConfig .builder ()
437+ .afterTimestamp (Instant .parse ("2024-12-12T12:00:12.000000Z" ))
438+ .build ();
439+
440+ Session session =
441+ vertexAiSessionService .getSession ("123" , "user" , "8" , Optional .of (config )).blockingGet ();
442+
443+ // afterTimestamp falls strictly between e2 and e3, so only e3 is kept.
444+ assertThat (session .events ().stream ().map (Event ::id )).containsExactly ("e3" );
445+ }
446+
447+ @ Test
448+ public void getSession_numRecentEventsConfig_returnsMostRecentEvents () {
449+ sessionMap .put ("7" , mockSessionJson ("7" , "2024-12-12T12:00:30.000000Z" ));
450+ eventMap .put (
451+ "7" ,
452+ mockEventsJson (
453+ mockEventJson ("e1" , "2024-12-12T12:00:05.000000Z" ),
454+ mockEventJson ("e2" , "2024-12-12T12:00:10.000000Z" ),
455+ mockEventJson ("e3" , "2024-12-12T12:00:15.000000Z" )));
456+ GetSessionConfig config = GetSessionConfig .builder ().numRecentEvents (2 ).build ();
457+
458+ Session session =
459+ vertexAiSessionService .getSession ("123" , "user" , "7" , Optional .of (config )).blockingGet ();
460+
461+ assertThat (session .events ().stream ().map (Event ::id )).containsExactly ("e2" , "e3" ).inOrder ();
462+ }
463+
464+ private static String mockSessionJson (String sessionId , String updateTime ) {
465+ return String .format (
466+ """
467+ {
468+ "name" : "reasoningEngines/123/sessions/%s",
469+ "userId" : "user",
470+ "updateTime" : "%s"
471+ }\
472+ """ ,
473+ sessionId , updateTime );
474+ }
475+
476+ private static String mockEventJson (String eventId , String timestamp ) {
477+ return String .format (
478+ """
479+ {
480+ "name" : "reasoningEngines/123/sessions/x/events/%s",
481+ "invocationId" : "%s",
482+ "author" : "agent",
483+ "timestamp" : "%s",
484+ "content" : { "role" : "model", "parts" : [ { "text" : "%s" } ] }
485+ }\
486+ """ ,
487+ eventId , eventId , timestamp , eventId );
488+ }
489+
490+ private static String mockEventsJson (String ... events ) {
491+ return "[" + String .join ("," , events ) + "]" ;
492+ }
382493}
0 commit comments