forked from github/copilot-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathClientLifecycleE2ETests.cs
More file actions
135 lines (113 loc) · 5.43 KB
/
Copy pathClientLifecycleE2ETests.cs
File metadata and controls
135 lines (113 loc) · 5.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
using GitHub.Copilot.Rpc;
using Xunit;
using Xunit.Abstractions;
namespace GitHub.Copilot.Test.E2E;
public class ClientLifecycleE2ETests(E2ETestFixture fixture, ITestOutputHelper output)
: E2ETestBase(fixture, "client_lifecycle", output)
{
[Fact]
public async Task Should_Receive_Session_Created_Lifecycle_Event()
{
var created = new TaskCompletionSource<SessionLifecycleEvent>(TaskCreationOptions.RunContinuationsAsynchronously);
using var subscription = Client.OnLifecycle<SessionLifecycleEvent>(evt =>
{
if (evt is SessionCreatedEvent)
{
created.TrySetResult(evt);
}
});
await using var session = await CreateSessionAsync();
var evt = await created.Task.WaitAsync(TimeSpan.FromSeconds(10));
Assert.IsType<SessionCreatedEvent>(evt);
Assert.Equal(session.SessionId, evt.SessionId);
}
[Fact]
public async Task Should_Filter_Session_Lifecycle_Events_By_Type()
{
var created = new TaskCompletionSource<SessionLifecycleEvent>(TaskCreationOptions.RunContinuationsAsynchronously);
using var subscription = Client.OnLifecycle<SessionCreatedEvent>(evt => created.TrySetResult(evt));
await using var session = await CreateSessionAsync();
var evt = await created.Task.WaitAsync(TimeSpan.FromSeconds(10));
Assert.IsType<SessionCreatedEvent>(evt);
Assert.Equal(session.SessionId, evt.SessionId);
}
[Fact]
public async Task Disposing_Lifecycle_Subscription_Stops_Receiving_Events()
{
var count = 0;
var created = new TaskCompletionSource<SessionLifecycleEvent>(TaskCreationOptions.RunContinuationsAsynchronously);
var subscription = Client.OnLifecycle<SessionLifecycleEvent>(_ => Interlocked.Increment(ref count));
subscription.Dispose();
using var activeSubscription = Client.OnLifecycle<SessionCreatedEvent>(evt => created.TrySetResult(evt));
await using var session = await CreateSessionAsync();
var evt = await created.Task.WaitAsync(TimeSpan.FromSeconds(10));
Assert.Equal(session.SessionId, evt.SessionId);
Assert.Equal(0, Interlocked.CompareExchange(ref count, 0, 0));
}
[Theory]
[InlineData(true)] // async dispose path (DisposeAsync)
[InlineData(false)] // sync dispose path (Dispose)
public async Task Dispose_Disconnects_Client_And_Disposes_Rpc_Surface(bool useAsyncDispose)
{
var client = Ctx.CreateClient();
await client.StartAsync();
if (useAsyncDispose)
{
await client.DisposeAsync();
}
else
{
client.Dispose();
}
Assert.Throws<ObjectDisposedException>(() => client.Rpc);
}
[Fact]
public async Task Should_Receive_Session_Updated_Lifecycle_Event_For_Non_Ephemeral_Activity()
{
await using var session = await CreateSessionAsync();
var updated = new TaskCompletionSource<SessionLifecycleEvent>(TaskCreationOptions.RunContinuationsAsynchronously);
using var subscription = Client.OnLifecycle<SessionUpdatedEvent>(evt =>
{
if (string.Equals(evt.SessionId, session.SessionId, StringComparison.Ordinal))
{
updated.TrySetResult(evt);
}
});
// session.mode.set emits a non-ephemeral session.mode_changed event,
// which the runtime forwards as session.updated to lifecycle subscribers.
await session.Rpc.Mode.SetAsync(SessionMode.Plan);
var evt = await updated.Task.WaitAsync(TimeSpan.FromSeconds(15));
Assert.IsType<SessionUpdatedEvent>(evt);
Assert.Equal(session.SessionId, evt.SessionId);
}
[Fact]
public async Task Should_Receive_Session_Deleted_Lifecycle_Event_When_Deleted()
{
var session = await CreateSessionAsync();
var sessionId = session.SessionId;
// The runtime persists session state to disk only after the first user.message
// (LocalSessionManager.SessionWriter gates flushing on shouldSaveSession).
// session.delete fails with "Session file not found" otherwise, so prime
// persistence with a real LLM round-trip first.
await session.SendAndWaitAsync(new MessageOptions { Prompt = "Say SESSION_DELETED_OK exactly." });
var deleted = new TaskCompletionSource<SessionLifecycleEvent>(TaskCreationOptions.RunContinuationsAsynchronously);
using var subscription = Client.OnLifecycle<SessionDeletedEvent>(evt =>
{
if (string.Equals(evt.SessionId, sessionId, StringComparison.Ordinal))
{
deleted.TrySetResult(evt);
}
});
// Do NOT DisposeAsync the session before deleting: dispose sends session.destroy
// which closes in-memory state but does not remove the disk file; calling
// delete afterwards still succeeds, but skipping dispose keeps the test minimal.
await Client.DeleteSessionAsync(sessionId);
var evt = await deleted.Task.WaitAsync(TimeSpan.FromSeconds(15));
Assert.IsType<SessionDeletedEvent>(evt);
Assert.Equal(sessionId, evt.SessionId);
await session.DisposeAsync();
}
}