From e460a781babc410f23441283b8f3170ee1247ba5 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Thu, 15 Jan 2026 07:48:48 -0800 Subject: [PATCH 1/3] Add session event subscription tests for all SDKs - Rename .NET test from Should_SessionEvt_Subscribed to Should_Receive_Session_Events - Add equivalent tests for Node.js, Python, and Go SDKs - Add shared snapshot file for test replay --- dotnet/test/SessionTests.cs | 2 +- go/e2e/session_test.go | 64 +++++++++++++++++++ nodejs/test/e2e/session.test.ts | 28 ++++++++ python/e2e/test_session.py | 30 +++++++++ .../should_receive_session_events.yaml | 11 ++++ 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 test/snapshots/session/should_receive_session_events.yaml diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index f3425c69..5facb030 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -287,7 +287,7 @@ public async Task Should_Pass_Streaming_Option_To_Session_Creation() } [Fact] - public async Task Should_SessionEvt_Subscribed() + public async Task Should_Receive_Session_Events() { var session = await Client.CreateSessionAsync(); var receivedEvents = new List(); diff --git a/go/e2e/session_test.go b/go/e2e/session_test.go index 310b78e3..8efbd921 100644 --- a/go/e2e/session_test.go +++ b/go/e2e/session_test.go @@ -614,6 +614,70 @@ func TestSession(t *testing.T) { t.Errorf("Expected assistant message to contain '2', got %v", assistantMessage.Data.Content) } }) + + t.Run("should receive session events", func(t *testing.T) { + ctx.ConfigureForTest(t) + + session, err := client.CreateSession(nil) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + var receivedEvents []copilot.SessionEvent + idle := make(chan bool) + + session.On(func(event copilot.SessionEvent) { + receivedEvents = append(receivedEvents, event) + if event.Type == "session.idle" { + select { + case idle <- true: + default: + } + } + }) + + // Send a message to trigger events + _, err = session.Send(copilot.MessageOptions{Prompt: "Hello!"}) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + + // Wait for session to become idle + select { + case <-idle: + case <-time.After(60 * time.Second): + t.Fatal("Timed out waiting for session.idle") + } + + // Should have received multiple events + if len(receivedEvents) == 0 { + t.Error("Expected to receive events, got none") + } + + hasUserMessage := false + hasAssistantMessage := false + hasSessionIdle := false + for _, evt := range receivedEvents { + switch evt.Type { + case "user.message": + hasUserMessage = true + case "assistant.message": + hasAssistantMessage = true + case "session.idle": + hasSessionIdle = true + } + } + + if !hasUserMessage { + t.Error("Expected to receive user.message event") + } + if !hasAssistantMessage { + t.Error("Expected to receive assistant.message event") + } + if !hasSessionIdle { + t.Error("Expected to receive session.idle event") + } + }) } func getSystemMessage(exchange testharness.ParsedHttpExchange) string { diff --git a/nodejs/test/e2e/session.test.ts b/nodejs/test/e2e/session.test.ts index 6beb41aa..e0f932e5 100644 --- a/nodejs/test/e2e/session.test.ts +++ b/nodejs/test/e2e/session.test.ts @@ -299,6 +299,34 @@ describe("Sessions", async () => { const assistantMessage = await getFinalAssistantMessage(session); expect(assistantMessage.data.content).toContain("2"); }); + + it("should receive session events", async () => { + const session = await client.createSession(); + const receivedEvents: Array<{ type: string }> = []; + let idleResolve: () => void; + const idlePromise = new Promise((resolve) => { + idleResolve = resolve; + }); + + session.on((event) => { + receivedEvents.push(event); + if (event.type === "session.idle") { + idleResolve(); + } + }); + + // Send a message to trigger events + await session.send({ prompt: "Hello!" }); + + // Wait for session to become idle + await Promise.race([idlePromise, new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 60000))]); + + // Should have received multiple events + expect(receivedEvents.length).toBeGreaterThan(0); + expect(receivedEvents.some((e) => e.type === "user.message")).toBe(true); + expect(receivedEvents.some((e) => e.type === "assistant.message")).toBe(true); + expect(receivedEvents.some((e) => e.type === "session.idle")).toBe(true); + }); }); function getSystemMessage(exchange: ParsedHttpExchange): string | undefined { diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py index 18d7ac0d..bd5e2a89 100644 --- a/python/e2e/test_session.py +++ b/python/e2e/test_session.py @@ -326,6 +326,36 @@ async def test_should_pass_streaming_option_to_session_creation(self, ctx: E2ETe assistant_message = await get_final_assistant_message(session) assert "2" in assistant_message.data.content + async def test_should_receive_session_events(self, ctx: E2ETestContext): + import asyncio + + session = await ctx.client.create_session() + received_events = [] + idle_event = asyncio.Event() + + def on_event(event): + received_events.append(event) + if event.type.value == "session.idle": + idle_event.set() + + session.on(on_event) + + # Send a message to trigger events + await session.send({"prompt": "Hello!"}) + + # Wait for session to become idle + try: + await asyncio.wait_for(idle_event.wait(), timeout=60) + except asyncio.TimeoutError: + pytest.fail("Timed out waiting for session.idle") + + # Should have received multiple events + assert len(received_events) > 0 + event_types = [e.type.value for e in received_events] + assert "user.message" in event_types + assert "assistant.message" in event_types + assert "session.idle" in event_types + def _get_system_message(exchange: dict) -> str: messages = exchange.get("request", {}).get("messages", []) diff --git a/test/snapshots/session/should_receive_session_events.yaml b/test/snapshots/session/should_receive_session_events.yaml new file mode 100644 index 00000000..68a1f096 --- /dev/null +++ b/test/snapshots/session/should_receive_session_events.yaml @@ -0,0 +1,11 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Hello! + - role: assistant + content: Hello! I'm GitHub Copilot CLI, your terminal assistant. How can I help you today with your software engineering + tasks? From 2b348b9062b84a7edae557ecfec6cd288982e6a8 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Thu, 15 Jan 2026 07:53:54 -0800 Subject: [PATCH 2/3] Update session event tests to assert response content - Change prompt to 'What is 100+200?' - Assert assistant response contains '300' - Regenerate snapshot file --- dotnet/test/SessionTests.cs | 7 ++++++- go/e2e/session_test.go | 11 ++++++++++- nodejs/test/e2e/session.test.ts | 6 +++++- python/e2e/test_session.py | 6 +++++- .../session/should_receive_session_events.yaml | 5 ++--- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index 5facb030..e72fe270 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -303,7 +303,7 @@ public async Task Should_Receive_Session_Events() }); // Send a message to trigger events - await session.SendAsync(new MessageOptions { Prompt = "Hello!" }); + await session.SendAsync(new MessageOptions { Prompt = "What is 100+200?" }); // Wait for session to become idle (indicating message processing is complete) var completed = await Task.WhenAny(idleReceived.Task, Task.Delay(TimeSpan.FromSeconds(60))); @@ -315,6 +315,11 @@ public async Task Should_Receive_Session_Events() Assert.Contains(receivedEvents, evt => evt is AssistantMessageEvent); Assert.Contains(receivedEvents, evt => evt is SessionIdleEvent); + // Verify the assistant response contains the expected answer + var assistantMessage = await TestHelper.GetFinalAssistantMessageAsync(session); + Assert.NotNull(assistantMessage); + Assert.Contains("300", assistantMessage!.Data.Content); + await session.DisposeAsync(); } } diff --git a/go/e2e/session_test.go b/go/e2e/session_test.go index 8efbd921..02cea5bd 100644 --- a/go/e2e/session_test.go +++ b/go/e2e/session_test.go @@ -637,7 +637,7 @@ func TestSession(t *testing.T) { }) // Send a message to trigger events - _, err = session.Send(copilot.MessageOptions{Prompt: "Hello!"}) + _, err = session.Send(copilot.MessageOptions{Prompt: "What is 100+200?"}) if err != nil { t.Fatalf("Failed to send message: %v", err) } @@ -677,6 +677,15 @@ func TestSession(t *testing.T) { if !hasSessionIdle { t.Error("Expected to receive session.idle event") } + + // Verify the assistant response contains the expected answer + assistantMessage, err := testharness.GetFinalAssistantMessage(session, 60*time.Second) + if err != nil { + t.Fatalf("Failed to get assistant message: %v", err) + } + if assistantMessage.Data.Content == nil || !strings.Contains(*assistantMessage.Data.Content, "300") { + t.Errorf("Expected assistant message to contain '300', got %v", assistantMessage.Data.Content) + } }) } diff --git a/nodejs/test/e2e/session.test.ts b/nodejs/test/e2e/session.test.ts index e0f932e5..e68a8026 100644 --- a/nodejs/test/e2e/session.test.ts +++ b/nodejs/test/e2e/session.test.ts @@ -316,7 +316,7 @@ describe("Sessions", async () => { }); // Send a message to trigger events - await session.send({ prompt: "Hello!" }); + await session.send({ prompt: "What is 100+200?" }); // Wait for session to become idle await Promise.race([idlePromise, new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 60000))]); @@ -326,6 +326,10 @@ describe("Sessions", async () => { expect(receivedEvents.some((e) => e.type === "user.message")).toBe(true); expect(receivedEvents.some((e) => e.type === "assistant.message")).toBe(true); expect(receivedEvents.some((e) => e.type === "session.idle")).toBe(true); + + // Verify the assistant response contains the expected answer + const assistantMessage = await getFinalAssistantMessage(session); + expect(assistantMessage.data.content).toContain("300"); }); }); diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py index bd5e2a89..30d24f60 100644 --- a/python/e2e/test_session.py +++ b/python/e2e/test_session.py @@ -341,7 +341,7 @@ def on_event(event): session.on(on_event) # Send a message to trigger events - await session.send({"prompt": "Hello!"}) + await session.send({"prompt": "What is 100+200?"}) # Wait for session to become idle try: @@ -356,6 +356,10 @@ def on_event(event): assert "assistant.message" in event_types assert "session.idle" in event_types + # Verify the assistant response contains the expected answer + assistant_message = await get_final_assistant_message(session) + assert "300" in assistant_message.data.content + def _get_system_message(exchange: dict) -> str: messages = exchange.get("request", {}).get("messages", []) diff --git a/test/snapshots/session/should_receive_session_events.yaml b/test/snapshots/session/should_receive_session_events.yaml index 68a1f096..229563a4 100644 --- a/test/snapshots/session/should_receive_session_events.yaml +++ b/test/snapshots/session/should_receive_session_events.yaml @@ -5,7 +5,6 @@ conversations: - role: system content: ${system} - role: user - content: Hello! + content: What is 100+200? - role: assistant - content: Hello! I'm GitHub Copilot CLI, your terminal assistant. How can I help you today with your software engineering - tasks? + content: 100 + 200 = 300 From b7dffcb35f388fde3f606946229b130b3ad93653 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Thu, 15 Jan 2026 07:58:22 -0800 Subject: [PATCH 3/3] Fix formatting in session.test.ts --- nodejs/test/e2e/session.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nodejs/test/e2e/session.test.ts b/nodejs/test/e2e/session.test.ts index e68a8026..a25f00c0 100644 --- a/nodejs/test/e2e/session.test.ts +++ b/nodejs/test/e2e/session.test.ts @@ -319,7 +319,10 @@ describe("Sessions", async () => { await session.send({ prompt: "What is 100+200?" }); // Wait for session to become idle - await Promise.race([idlePromise, new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 60000))]); + await Promise.race([ + idlePromise, + new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 60000)), + ]); // Should have received multiple events expect(receivedEvents.length).toBeGreaterThan(0);