-
Notifications
You must be signed in to change notification settings - Fork 891
fix(a2a): use protocol-standard contextId as sessionId instead of req… #1827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -551,6 +551,150 @@ void testHandleExceptionDuringTaskCancellation() throws JSONRPCError { | |
| } | ||
| } | ||
|
|
||
| @Nested | ||
| @DisplayName("Session ID Resolution Tests") | ||
| class SessionIdResolutionTests { | ||
|
|
||
| @Test | ||
| @DisplayName("Should use contextId as sessionId when contextId is present") | ||
| void testSessionIdFromContextId() throws JSONRPCError { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nitpick] Test code duplication: The three test cases share extensive mock setup boilerplate (
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [recommended] Test coverage gap: The existing 3 tests cover contextId present, metadata fallback, and both present. Consider adding a test for the edge case where
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nitpick] Test code duplication: The three test cases share extensive mock setup boilerplate ( |
||
| // Given: contextId is set, no metadata sessionId | ||
| String taskId = UUID.randomUUID().toString(); | ||
| String contextId = "context-session-123"; | ||
|
|
||
| when(mockContext.getTaskId()).thenReturn(taskId); | ||
| when(mockContext.getContextId()).thenReturn(contextId); | ||
|
|
||
| Message mockMessage = mock(Message.class); | ||
| when(mockMessage.getTaskId()).thenReturn(taskId); | ||
| when(mockMessage.getContextId()).thenReturn(contextId); | ||
| when(mockMessage.getParts()).thenReturn(List.of(new TextPart("hello"))); | ||
| when(mockMessage.getMetadata()).thenReturn(null); // No metadata at all | ||
| when(mockContext.getMessage()).thenReturn(mockMessage); | ||
|
|
||
| MessageSendParams mockParams = mock(MessageSendParams.class); | ||
| when(mockContext.getParams()).thenReturn(mockParams); | ||
| when(mockParams.message()).thenReturn(mockMessage); | ||
|
|
||
| when(mockContext.getCallContext()).thenReturn(serverCallContext); | ||
| when(serverCallContext.getState()).thenReturn(Map.of()); | ||
|
|
||
| AtomicReference<AgentRequestOptions> capturedOptions = new AtomicReference<>(); | ||
| when(mockAgentRunner.stream(anyList(), any(AgentRequestOptions.class))) | ||
| .thenAnswer( | ||
| invocation -> { | ||
| capturedOptions.set(invocation.getArgument(1)); | ||
| return Flux.just( | ||
| new Event( | ||
| EventType.REASONING, | ||
| Msg.builder().textContent("response").build(), | ||
| true)); | ||
| }); | ||
|
|
||
| doAnswer(invocation -> null).when(mockEventQueue).enqueueEvent(any(Message.class)); | ||
|
|
||
| // When | ||
| executor.execute(mockContext, mockEventQueue); | ||
|
|
||
| // Then: sessionId should come from contextId | ||
| assertNotNull(capturedOptions.get()); | ||
| assertEquals(contextId, capturedOptions.get().getSessionId()); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Should fallback to metadata sessionId when contextId is empty") | ||
| void testSessionIdFallbackToMetadata() throws JSONRPCError { | ||
| // Given: contextId is empty, metadata has sessionId | ||
| String taskId = UUID.randomUUID().toString(); | ||
| String metadataSessionId = "metadata-session-456"; | ||
|
|
||
| when(mockContext.getTaskId()).thenReturn(taskId); | ||
| when(mockContext.getContextId()).thenReturn(""); // Empty contextId | ||
|
|
||
| Message mockMessage = mock(Message.class); | ||
| when(mockMessage.getTaskId()).thenReturn(taskId); | ||
| when(mockMessage.getContextId()).thenReturn(""); | ||
| when(mockMessage.getParts()).thenReturn(List.of(new TextPart("hello"))); | ||
| when(mockMessage.getMetadata()).thenReturn(Map.of("sessionId", metadataSessionId)); | ||
| when(mockContext.getMessage()).thenReturn(mockMessage); | ||
|
|
||
| MessageSendParams mockParams = mock(MessageSendParams.class); | ||
| when(mockContext.getParams()).thenReturn(mockParams); | ||
| when(mockParams.message()).thenReturn(mockMessage); | ||
|
|
||
| when(mockContext.getCallContext()).thenReturn(serverCallContext); | ||
| when(serverCallContext.getState()).thenReturn(Map.of()); | ||
|
|
||
| AtomicReference<AgentRequestOptions> capturedOptions = new AtomicReference<>(); | ||
| when(mockAgentRunner.stream(anyList(), any(AgentRequestOptions.class))) | ||
| .thenAnswer( | ||
| invocation -> { | ||
| capturedOptions.set(invocation.getArgument(1)); | ||
| return Flux.just( | ||
| new Event( | ||
| EventType.REASONING, | ||
| Msg.builder().textContent("response").build(), | ||
| true)); | ||
| }); | ||
|
|
||
| doAnswer(invocation -> null).when(mockEventQueue).enqueueEvent(any(Message.class)); | ||
|
|
||
| // When | ||
| executor.execute(mockContext, mockEventQueue); | ||
|
|
||
| // Then: sessionId should fallback to metadata | ||
| assertNotNull(capturedOptions.get()); | ||
| assertEquals(metadataSessionId, capturedOptions.get().getSessionId()); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Should prefer contextId over metadata sessionId when both present") | ||
| void testContextIdTakesPrecedenceOverMetadata() throws JSONRPCError { | ||
| // Given: both contextId and metadata sessionId are set | ||
| String taskId = UUID.randomUUID().toString(); | ||
| String contextId = "context-id-wins"; | ||
| String metadataSessionId = "metadata-id-loses"; | ||
|
|
||
| when(mockContext.getTaskId()).thenReturn(taskId); | ||
| when(mockContext.getContextId()).thenReturn(contextId); | ||
|
|
||
| Message mockMessage = mock(Message.class); | ||
| when(mockMessage.getTaskId()).thenReturn(taskId); | ||
| when(mockMessage.getContextId()).thenReturn(contextId); | ||
| when(mockMessage.getParts()).thenReturn(List.of(new TextPart("hello"))); | ||
| when(mockMessage.getMetadata()).thenReturn(Map.of("sessionId", metadataSessionId)); | ||
| when(mockContext.getMessage()).thenReturn(mockMessage); | ||
|
|
||
| MessageSendParams mockParams = mock(MessageSendParams.class); | ||
| when(mockContext.getParams()).thenReturn(mockParams); | ||
| when(mockParams.message()).thenReturn(mockMessage); | ||
|
|
||
| when(mockContext.getCallContext()).thenReturn(serverCallContext); | ||
| when(serverCallContext.getState()).thenReturn(Map.of()); | ||
|
|
||
| AtomicReference<AgentRequestOptions> capturedOptions = new AtomicReference<>(); | ||
| when(mockAgentRunner.stream(anyList(), any(AgentRequestOptions.class))) | ||
| .thenAnswer( | ||
| invocation -> { | ||
| capturedOptions.set(invocation.getArgument(1)); | ||
| return Flux.just( | ||
| new Event( | ||
| EventType.REASONING, | ||
| Msg.builder().textContent("response").build(), | ||
| true)); | ||
| }); | ||
|
|
||
| doAnswer(invocation -> null).when(mockEventQueue).enqueueEvent(any(Message.class)); | ||
|
|
||
| // When | ||
| executor.execute(mockContext, mockEventQueue); | ||
|
|
||
| // Then: contextId should take precedence | ||
| assertNotNull(capturedOptions.get()); | ||
| assertEquals(contextId, capturedOptions.get().getSessionId()); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Should handle exception during execution") | ||
| void testHandleExceptionDuringExecution() throws JSONRPCError { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[recommended] Test coverage gap: The existing 3 tests cover contextId present, metadata fallback, and both present. Consider adding a test for the edge case where
context.getContextId()returnsnull(not empty string) and no metadata sessionId exists — verifying the method returns empty string. This path is handled correctly in the code (contextId != nullcheck) but has no test.