Skip to content

Commit 03f0f60

Browse files
committed
Add end-to-end tests for session event lifecycle in SessionEventsE2ETest
1 parent 03b1a9d commit 03f0f60

1 file changed

Lines changed: 227 additions & 0 deletions

File tree

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.sdk;
6+
7+
import static org.junit.jupiter.api.Assertions.assertFalse;
8+
import static org.junit.jupiter.api.Assertions.assertTrue;
9+
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.concurrent.TimeUnit;
15+
16+
import org.junit.jupiter.api.AfterAll;
17+
import org.junit.jupiter.api.BeforeAll;
18+
import org.junit.jupiter.api.Test;
19+
20+
import com.github.copilot.sdk.events.AbstractSessionEvent;
21+
import com.github.copilot.sdk.events.AssistantMessageEvent;
22+
import com.github.copilot.sdk.events.AssistantTurnEndEvent;
23+
import com.github.copilot.sdk.events.AssistantTurnStartEvent;
24+
import com.github.copilot.sdk.events.AssistantUsageEvent;
25+
import com.github.copilot.sdk.events.SessionIdleEvent;
26+
import com.github.copilot.sdk.events.ToolExecutionCompleteEvent;
27+
import com.github.copilot.sdk.events.ToolExecutionStartEvent;
28+
import com.github.copilot.sdk.events.UserMessageEvent;
29+
import com.github.copilot.sdk.json.MessageOptions;
30+
31+
/**
32+
* E2E tests for session events to verify event lifecycle.
33+
* <p>
34+
* These tests verify that various session events are properly emitted during
35+
* typical interaction flows with the Copilot CLI.
36+
* </p>
37+
*/
38+
public class SessionEventsE2ETest {
39+
40+
private static E2ETestContext ctx;
41+
42+
@BeforeAll
43+
static void setup() throws Exception {
44+
ctx = E2ETestContext.create();
45+
}
46+
47+
@AfterAll
48+
static void teardown() throws Exception {
49+
if (ctx != null) {
50+
ctx.close();
51+
}
52+
}
53+
54+
@Test
55+
void testAssistantTurnEventsEmitted() throws Exception {
56+
ctx.configureForTest("events", "assistant_turn_events_emitted");
57+
58+
List<AbstractSessionEvent> allEvents = new ArrayList<>();
59+
60+
try (CopilotClient client = ctx.createClient()) {
61+
CopilotSession session = client.createSession().get();
62+
63+
session.on(event -> allEvents.add(event));
64+
65+
session.sendAndWait(new MessageOptions().setPrompt("Say hello")).get(60, TimeUnit.SECONDS);
66+
67+
// Verify turn lifecycle events
68+
assertTrue(allEvents.stream().anyMatch(e -> e instanceof AssistantTurnStartEvent),
69+
"Should receive assistant.turn_start event");
70+
assertTrue(allEvents.stream().anyMatch(e -> e instanceof AssistantTurnEndEvent),
71+
"Should receive assistant.turn_end event");
72+
73+
// Verify order: turn_start should come before turn_end
74+
int turnStartIndex = -1;
75+
int turnEndIndex = -1;
76+
for (int i = 0; i < allEvents.size(); i++) {
77+
if (allEvents.get(i) instanceof AssistantTurnStartEvent && turnStartIndex == -1) {
78+
turnStartIndex = i;
79+
}
80+
if (allEvents.get(i) instanceof AssistantTurnEndEvent) {
81+
turnEndIndex = i;
82+
}
83+
}
84+
assertTrue(turnStartIndex < turnEndIndex, "turn_start should come before turn_end");
85+
}
86+
}
87+
88+
@Test
89+
void testUserMessageEventEmitted() throws Exception {
90+
ctx.configureForTest("events", "user_message_event_emitted");
91+
92+
List<UserMessageEvent> userMessages = new ArrayList<>();
93+
94+
try (CopilotClient client = ctx.createClient()) {
95+
CopilotSession session = client.createSession().get();
96+
97+
session.on(UserMessageEvent.class, userMessages::add);
98+
99+
session.sendAndWait(new MessageOptions().setPrompt("Hello, Copilot!")).get(60, TimeUnit.SECONDS);
100+
101+
// Verify user message was captured
102+
assertFalse(userMessages.isEmpty(), "Should receive user.message event");
103+
}
104+
}
105+
106+
@Test
107+
void testToolExecutionCompleteEventEmitted() throws Exception {
108+
ctx.configureForTest("events", "tool_execution_complete_event_emitted");
109+
110+
List<ToolExecutionStartEvent> toolStarts = new ArrayList<>();
111+
List<ToolExecutionCompleteEvent> toolCompletes = new ArrayList<>();
112+
113+
try (CopilotClient client = ctx.createClient()) {
114+
CopilotSession session = client.createSession().get();
115+
116+
session.on(ToolExecutionStartEvent.class, toolStarts::add);
117+
session.on(ToolExecutionCompleteEvent.class, toolCompletes::add);
118+
119+
// Create a file for the model to read
120+
Path testFile = ctx.getWorkDir().resolve("test-events.txt");
121+
Files.writeString(testFile, "Event test content");
122+
123+
session.sendAndWait(new MessageOptions().setPrompt("Read the contents of test-events.txt")).get(60,
124+
TimeUnit.SECONDS);
125+
126+
// Verify tool execution events
127+
assertFalse(toolStarts.isEmpty(), "Should receive tool.execution_start event");
128+
assertFalse(toolCompletes.isEmpty(), "Should receive tool.execution_complete event");
129+
130+
// Verify tool execution completed successfully
131+
assertTrue(toolCompletes.stream().anyMatch(e -> e.getData().isSuccess()),
132+
"At least one tool execution should be successful");
133+
}
134+
}
135+
136+
@Test
137+
void testAssistantUsageEventEmitted() throws Exception {
138+
ctx.configureForTest("events", "assistant_usage_event_emitted");
139+
140+
List<AssistantUsageEvent> usageEvents = new ArrayList<>();
141+
142+
try (CopilotClient client = ctx.createClient()) {
143+
CopilotSession session = client.createSession().get();
144+
145+
session.on(AssistantUsageEvent.class, usageEvents::add);
146+
147+
session.sendAndWait(new MessageOptions().setPrompt("What is 2+2?")).get(60, TimeUnit.SECONDS);
148+
149+
// Usage events may or may not be emitted depending on the model/API version
150+
// This test verifies the event handler works when they are emitted
151+
// We don't assert they must be present since it depends on the backend
152+
}
153+
}
154+
155+
@Test
156+
void testSessionIdleEventAfterMessageComplete() throws Exception {
157+
ctx.configureForTest("events", "session_idle_after_message");
158+
159+
List<AbstractSessionEvent> allEvents = new ArrayList<>();
160+
161+
try (CopilotClient client = ctx.createClient()) {
162+
CopilotSession session = client.createSession().get();
163+
164+
session.on(event -> allEvents.add(event));
165+
166+
session.sendAndWait(new MessageOptions().setPrompt("Say OK")).get(60, TimeUnit.SECONDS);
167+
168+
// Verify session.idle is emitted after assistant.message
169+
assertTrue(allEvents.stream().anyMatch(e -> e instanceof SessionIdleEvent),
170+
"Should receive session.idle event");
171+
assertTrue(allEvents.stream().anyMatch(e -> e instanceof AssistantMessageEvent),
172+
"Should receive assistant.message event");
173+
174+
// Verify order: assistant.message should come before session.idle
175+
int messageIndex = -1;
176+
int idleIndex = -1;
177+
for (int i = 0; i < allEvents.size(); i++) {
178+
if (allEvents.get(i) instanceof AssistantMessageEvent) {
179+
messageIndex = i;
180+
}
181+
if (allEvents.get(i) instanceof SessionIdleEvent) {
182+
idleIndex = i;
183+
}
184+
}
185+
assertTrue(messageIndex < idleIndex, "assistant.message should come before session.idle");
186+
}
187+
}
188+
189+
@Test
190+
void testEventOrderDuringToolExecution() throws Exception {
191+
ctx.configureForTest("events", "event_order_during_tool_execution");
192+
193+
List<String> eventTypes = new ArrayList<>();
194+
195+
try (CopilotClient client = ctx.createClient()) {
196+
CopilotSession session = client.createSession().get();
197+
198+
session.on(event -> eventTypes.add(event.getType()));
199+
200+
// Create a file for the model to read
201+
Path testFile = ctx.getWorkDir().resolve("order-test.txt");
202+
Files.writeString(testFile, "Order test content");
203+
204+
session.sendAndWait(new MessageOptions().setPrompt("Read the contents of order-test.txt")).get(60,
205+
TimeUnit.SECONDS);
206+
207+
// Verify expected event types are present
208+
assertTrue(eventTypes.contains("user.message"), "Should have user.message");
209+
assertTrue(eventTypes.contains("assistant.turn_start"), "Should have assistant.turn_start");
210+
assertTrue(eventTypes.contains("tool.execution_start"), "Should have tool.execution_start");
211+
assertTrue(eventTypes.contains("tool.execution_complete"), "Should have tool.execution_complete");
212+
assertTrue(eventTypes.contains("assistant.message"), "Should have assistant.message");
213+
assertTrue(eventTypes.contains("assistant.turn_end"), "Should have assistant.turn_end");
214+
assertTrue(eventTypes.contains("session.idle"), "Should have session.idle");
215+
216+
// Verify tool execution is between turn_start and turn_end
217+
int turnStartIdx = eventTypes.indexOf("assistant.turn_start");
218+
int toolStartIdx = eventTypes.indexOf("tool.execution_start");
219+
int toolCompleteIdx = eventTypes.indexOf("tool.execution_complete");
220+
int turnEndIdx = eventTypes.lastIndexOf("assistant.turn_end");
221+
222+
assertTrue(turnStartIdx < toolStartIdx, "turn_start should be before tool.execution_start");
223+
assertTrue(toolStartIdx < toolCompleteIdx, "tool.execution_start should be before tool.execution_complete");
224+
assertTrue(toolCompleteIdx < turnEndIdx, "tool.execution_complete should be before turn_end");
225+
}
226+
}
227+
}

0 commit comments

Comments
 (0)