-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathsession_lifecycle.e2e.test.ts
More file actions
152 lines (121 loc) · 6.16 KB
/
session_lifecycle.e2e.test.ts
File metadata and controls
152 lines (121 loc) · 6.16 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
import { describe, expect, it } from "vitest";
import { SessionEvent, approveAll } from "../../src/index.js";
import { createSdkTestContext } from "./harness/sdkTestContext";
/**
* Polls until predicate returns true or deadline expires. Used in lieu of arbitrary
* `setTimeout` waits for "session flushed to disk" so fast machines exit immediately
* and slow CI machines still get up to `timeoutMs` before the test fails.
*/
async function waitFor(
predicate: () => Promise<boolean> | boolean,
timeoutMs = 10_000
): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
if (await predicate()) return;
await new Promise((r) => setTimeout(r, 50));
}
throw new Error(`waitFor: condition not met within ${timeoutMs}ms`);
}
describe("Session Lifecycle", async () => {
const { copilotClient: client } = await createSdkTestContext();
it("should list created sessions after sending a message", async () => {
const session1 = await client.createSession({ onPermissionRequest: approveAll });
const session2 = await client.createSession({ onPermissionRequest: approveAll });
// Sessions must have activity to be persisted to disk
await session1.sendAndWait({ prompt: "Say hello" });
await session2.sendAndWait({ prompt: "Say world" });
// Poll until both sessions are visible on disk instead of a hard 500ms wait.
await waitFor(async () => {
const ids = (await client.listSessions()).map((s) => s.sessionId);
return ids.includes(session1.sessionId) && ids.includes(session2.sessionId);
});
const sessions = await client.listSessions();
const sessionIds = sessions.map((s) => s.sessionId);
expect(sessionIds).toContain(session1.sessionId);
expect(sessionIds).toContain(session2.sessionId);
await session1.disconnect();
await session2.disconnect();
});
it("should delete session permanently", async () => {
const session = await client.createSession({ onPermissionRequest: approveAll });
const sessionId = session.sessionId;
// Send a message so the session is persisted
await session.sendAndWait({ prompt: "Say hi" });
// Poll until the session is visible on disk instead of a hard 500ms wait.
await waitFor(async () => {
const ids = (await client.listSessions()).map((s) => s.sessionId);
return ids.includes(sessionId);
});
// Verify it appears in the list
const before = await client.listSessions();
expect(before.map((s) => s.sessionId)).toContain(sessionId);
await session.disconnect();
await client.deleteSession(sessionId);
// After delete, the session should not be in the list
const after = await client.listSessions();
expect(after.map((s) => s.sessionId)).not.toContain(sessionId);
});
it("should return events via getMessages after conversation", async () => {
const session = await client.createSession({ onPermissionRequest: approveAll });
await session.sendAndWait({
prompt: "What is 2+2? Reply with just the number.",
});
const messages = await session.getEvents();
expect(messages.length).toBeGreaterThan(0);
// Should have at least session.start, user.message, assistant.message, session.idle
const types = messages.map((m: SessionEvent) => m.type);
expect(types).toContain("session.start");
expect(types).toContain("user.message");
expect(types).toContain("assistant.message");
await session.disconnect();
});
it("should support multiple concurrent sessions", async () => {
const session1 = await client.createSession({ onPermissionRequest: approveAll });
const session2 = await client.createSession({ onPermissionRequest: approveAll });
// Send to both sessions
const [msg1, msg2] = await Promise.all([
session1.sendAndWait({ prompt: "What is 1+1? Reply with just the number." }),
session2.sendAndWait({ prompt: "What is 3+3? Reply with just the number." }),
]);
expect(msg1?.data.content).toContain("2");
expect(msg2?.data.content).toContain("6");
await session1.disconnect();
await session2.disconnect();
});
it("should isolate events between concurrent sessions", async () => {
const session1 = await client.createSession({ onPermissionRequest: approveAll });
const session2 = await client.createSession({ onPermissionRequest: approveAll });
const events1: SessionEvent[] = [];
const events2: SessionEvent[] = [];
session1.on((event) => events1.push(event));
session2.on((event) => events2.push(event));
const [msg1, msg2] = await Promise.all([
session1.sendAndWait({
prompt: "Say 'session_one_response'.",
}),
session2.sendAndWait({
prompt: "Say 'session_two_response'.",
}),
]);
expect(msg1?.data.content).toContain("session_one_response");
expect(msg2?.data.content).toContain("session_two_response");
// Session 1's events should not contain session 2's response text
const session1AssistantContent = events1
.filter((e) => e.type === "assistant.message")
.map((e) => e.data.content ?? "")
.join(" ");
expect(session1AssistantContent).not.toContain("session_two_response");
// Session 2's events should not contain session 1's response text
const session2AssistantContent = events2
.filter((e) => e.type === "assistant.message")
.map((e) => e.data.content ?? "")
.join(" ");
expect(session2AssistantContent).not.toContain("session_one_response");
await session1.disconnect();
await session2.disconnect();
});
});