
























@@ -11,6 +11,48 @@ import {
1111verifyTalkHandoffToken,
1212} from "./talk-handoff.js";
131314+function requireRecord(value: unknown, label: string): Record<string, unknown> {
15+if (!value || typeof value !== "object" || Array.isArray(value)) {
16+throw new Error(`Expected ${label}`);
17+}
18+return value as Record<string, unknown>;
19+}
20+21+function requireArray(value: unknown, label: string): unknown[] {
22+if (!Array.isArray(value)) {
23+throw new Error(`Expected ${label}`);
24+}
25+return value;
26+}
27+28+function expectFields(
29+value: unknown,
30+label: string,
31+fields: Record<string, unknown>,
32+): Record<string, unknown> {
33+const record = requireRecord(value, label);
34+for (const [key, expected] of Object.entries(fields)) {
35+expect(record[key]).toEqual(expected);
36+}
37+return record;
38+}
39+40+function requireRoom(value: unknown, label = "handoff room"): Record<string, unknown> {
41+return requireRecord(requireRecord(value, label).room, `${label} room`);
42+}
43+44+function requireEvents(value: unknown, label = "handoff result"): unknown[] {
45+return requireArray(requireRecord(value, label).events, `${label} events`);
46+}
47+48+function expectEventFields(
49+events: unknown[],
50+index: number,
51+fields: Record<string, unknown>,
52+): Record<string, unknown> {
53+return expectFields(events[index], `event ${index}`, fields);
54+}
55+1456describe("talk handoff store", () => {
1557it("creates an expiring managed-room handoff without storing the plaintext token", () => {
1658vi.useFakeTimers();
@@ -29,7 +71,7 @@ describe("talk handoff store", () => {
2971});
3072const record = getTalkHandoff(handoff.id);
317332-expect(handoff).toMatchObject({
74+const handoffRecord = expectFields(handoff, "created handoff", {
3375roomId: `talk_${handoff.id}`,
3476roomUrl: `/talk/rooms/talk_${handoff.id}`,
3577sessionKey: "session:main",
@@ -44,16 +86,14 @@ describe("talk handoff store", () => {
4486brain: "agent-consult",
4587createdAt: Date.parse("2026-05-05T12:00:00.000Z"),
4688expiresAt: Date.parse("2026-05-05T12:00:05.000Z"),
47-room: {
48-activeClientId: undefined,
49-recentTalkEvents: [
50-expect.objectContaining({
51-type: "session.started",
52-sessionId: `talk_${handoff.id}`,
53-transport: "managed-room",
54-}),
55-],
56-},
89+});
90+const room = requireRecord(handoffRecord.room, "created handoff room");
91+expect(room.activeClientId).toBeUndefined();
92+const events = requireArray(room.recentTalkEvents, "recent talk events");
93+expectEventFields(events, 0, {
94+type: "session.started",
95+sessionId: `talk_${handoff.id}`,
96+transport: "managed-room",
5797});
5898expect(handoff).not.toHaveProperty("tokenHash");
5999if (record === undefined) {
@@ -75,17 +115,18 @@ describe("talk handoff store", () => {
75115ok: false,
76116reason: "invalid_token",
77117});
78-expect(joinTalkHandoff(handoff.id, handoff.token)).toMatchObject({
118+const join = joinTalkHandoff(handoff.id, handoff.token);
119+const joinRecord = expectFields(join, "join result", {
79120ok: true,
80- events: [expect.objectContaining({ type: "session.ready" })],
81- record: expect.objectContaining({
82- id: handoff.id,
83- roomId: handoff.roomId,
84- sessionKey: "session:main",
85-}),
121+});
122+expectEventFields(requireEvents(joinRecord), 0, { type: "session.ready" });
123+expectFields(joinRecord.record, "joined record", {
124+id: handoff.id,
125+roomId: handoff.roomId,
126+sessionKey: "session:main",
86127});
8712888-expect(revokeTalkHandoff(handoff.id)).toMatchObject({ revoked: true });
129+expectFields(revokeTalkHandoff(handoff.id), "revoke result", { revoked: true });
89130expect(joinTalkHandoff(handoff.id, handoff.token)).toEqual({
90131ok: false,
91132reason: "not_found",
@@ -97,59 +138,53 @@ describe("talk handoff store", () => {
97138const handoff = createTalkHandoff({ sessionKey: "session:main" });
9813999140const firstJoin = joinTalkHandoff(handoff.id, handoff.token, { clientId: "conn-1" });
100-expect(firstJoin).toMatchObject({
141+expectFields(firstJoin, "first join", {
101142ok: true,
102-events: [
103-expect.objectContaining({
104-type: "session.ready",
105-sessionId: handoff.roomId,
106-payload: expect.objectContaining({ clientId: "conn-1" }),
107-}),
108-],
109-record: {
110-room: expect.objectContaining({
111-activeClientId: "conn-1",
112-}),
113-},
114143});
144+const firstReady = expectEventFields(requireEvents(firstJoin, "first join"), 0, {
145+type: "session.ready",
146+sessionId: handoff.roomId,
147+});
148+expect(requireRecord(firstReady.payload, "first ready payload").clientId).toBe("conn-1");
149+expect(
150+requireRoom(requireRecord(firstJoin, "first join").record, "first join record")
151+.activeClientId,
152+).toBe("conn-1");
115153116154const secondJoin = joinTalkHandoff(handoff.id, handoff.token, { clientId: "conn-2" });
117-expect(secondJoin).toMatchObject({
155+expectFields(secondJoin, "second join", {
118156ok: true,
119-events: [
120-expect.objectContaining({
121-type: "session.replaced",
122-sessionId: handoff.roomId,
123-payload: expect.objectContaining({
124-previousClientId: "conn-1",
125-nextClientId: "conn-2",
126-}),
127-}),
128-expect.objectContaining({
129-type: "session.ready",
130-sessionId: handoff.roomId,
131-payload: expect.objectContaining({ clientId: "conn-2" }),
132-}),
133-],
134-record: {
135-room: expect.objectContaining({
136-activeClientId: "conn-2",
137-}),
138-},
139157});
158+const secondEvents = requireEvents(secondJoin, "second join");
159+const replaced = expectEventFields(secondEvents, 0, {
160+type: "session.replaced",
161+sessionId: handoff.roomId,
162+});
163+expectFields(requireRecord(replaced.payload, "replaced payload"), "replaced payload", {
164+previousClientId: "conn-1",
165+nextClientId: "conn-2",
166+});
167+const ready = expectEventFields(secondEvents, 1, {
168+type: "session.ready",
169+sessionId: handoff.roomId,
170+});
171+expect(requireRecord(ready.payload, "ready payload").clientId).toBe("conn-2");
172+expect(
173+requireRoom(requireRecord(secondJoin, "second join").record, "second join record")
174+.activeClientId,
175+).toBe("conn-2");
140176141-expect(revokeTalkHandoff(handoff.id)).toMatchObject({
177+const revoked = revokeTalkHandoff(handoff.id);
178+expectFields(revoked, "revoke result", {
142179revoked: true,
143180activeClientId: "conn-2",
144-events: [
145-expect.objectContaining({
146-type: "session.closed",
147-sessionId: handoff.roomId,
148-payload: expect.objectContaining({ reason: "revoked" }),
149-final: true,
150-}),
151-],
152181});
182+const closed = expectEventFields(requireEvents(revoked, "revoke result"), 0, {
183+type: "session.closed",
184+sessionId: handoff.roomId,
185+final: true,
186+});
187+expect(requireRecord(closed.payload, "closed payload").reason).toBe("revoked");
153188});
154189155190it("records managed-room turn start, end, and cancellation events", () => {
@@ -161,53 +196,54 @@ describe("talk handoff store", () => {
161196clientId: "conn-1",
162197turnId: "turn-1",
163198});
164-expect(start).toMatchObject({
199+expectFields(start, "turn start", {
165200ok: true,
166201turnId: "turn-1",
167-events: [expect.objectContaining({ type: "turn.started", turnId: "turn-1" })],
168-record: {
169-room: expect.objectContaining({
170-activeClientId: "conn-1",
171-activeTurnId: "turn-1",
172-}),
173-},
174202});
203+expectEventFields(requireEvents(start, "turn start"), 0, {
204+type: "turn.started",
205+turnId: "turn-1",
206+});
207+expectFields(
208+requireRoom(requireRecord(start, "turn start").record, "turn start record"),
209+"turn room",
210+{
211+activeClientId: "conn-1",
212+activeTurnId: "turn-1",
213+},
214+);
175215176-expect(endTalkHandoffTurn(handoff.id, handoff.token)).toMatchObject({
216+const ended = endTalkHandoffTurn(handoff.id, handoff.token);
217+expectFields(ended, "turn end", {
177218ok: true,
178219turnId: "turn-1",
179-events: [
180-expect.objectContaining({
181-type: "turn.ended",
182-turnId: "turn-1",
183-final: true,
184-}),
185-],
186-record: {
187-room: expect.not.objectContaining({
188-activeTurnId: expect.any(String),
189-}),
190-},
191220});
221+expectEventFields(requireEvents(ended, "turn end"), 0, {
222+type: "turn.ended",
223+turnId: "turn-1",
224+final: true,
225+});
226+expect(
227+requireRoom(requireRecord(ended, "turn end").record, "turn end record").activeTurnId,
228+).toBeUndefined();
192229193230expect(cancelTalkHandoffTurn(handoff.id, handoff.token)).toEqual({
194231ok: false,
195232reason: "no_active_turn",
196233});
197234198235startTalkHandoffTurn(handoff.id, handoff.token, { turnId: "turn-2" });
199-expect(cancelTalkHandoffTurn(handoff.id, handoff.token, { reason: "barge-in" })).toMatchObject({
236+const cancelled = cancelTalkHandoffTurn(handoff.id, handoff.token, { reason: "barge-in" });
237+expectFields(cancelled, "turn cancellation", {
200238ok: true,
201239turnId: "turn-2",
202-events: [
203-expect.objectContaining({
204-type: "turn.cancelled",
205-turnId: "turn-2",
206-final: true,
207-payload: expect.objectContaining({ reason: "barge-in" }),
208-}),
209-],
210240});
241+const cancelledEvent = expectEventFields(requireEvents(cancelled, "turn cancellation"), 0, {
242+type: "turn.cancelled",
243+turnId: "turn-2",
244+final: true,
245+});
246+expect(requireRecord(cancelledEvent.payload, "cancelled payload").reason).toBe("barge-in");
211247});
212248213249it("rejects stale managed-room turn completion without clearing the active turn", () => {
@@ -229,7 +265,9 @@ describe("talk handoff store", () => {
229265});
230266expect(getTalkHandoff(handoff.id)?.room.talk.activeTurnId).toBe("turn-current");
231267232-expect(endTalkHandoffTurn(handoff.id, handoff.token, { turnId: "turn-current" })).toMatchObject(
268+expectFields(
269+endTalkHandoffTurn(handoff.id, handoff.token, { turnId: "turn-current" }),
270+"current turn end",
233271{
234272ok: true,
235273turnId: "turn-current",
@@ -263,26 +301,28 @@ describe("talk handoff store", () => {
263301ok: false,
264302reason: "invalid_token",
265303});
266-expect(joinTalkHandoff(first.id, first.token)).toMatchObject({
304+const firstJoin = joinTalkHandoff(first.id, first.token);
305+const firstJoinRecord = expectFields(firstJoin, "first join", {
267306ok: true,
268-events: [expect.objectContaining({ type: "session.ready" })],
269-record: expect.objectContaining({
270-roomId: first.roomId,
271-sessionKey: "agent:main:first",
272-channel: "browser",
273-target: "host:local",
274-provider: "openai",
275-}),
276-});
277-expect(joinTalkHandoff(second.id, second.token)).toMatchObject({
307+});
308+expectEventFields(requireEvents(firstJoin), 0, { type: "session.ready" });
309+expectFields(firstJoinRecord.record, "first joined record", {
310+roomId: first.roomId,
311+sessionKey: "agent:main:first",
312+channel: "browser",
313+target: "host:local",
314+provider: "openai",
315+});
316+const secondJoin = joinTalkHandoff(second.id, second.token);
317+const secondJoinRecord = expectFields(secondJoin, "second join", {
278318ok: true,
279- events: [expect.objectContaining({ type: "session.ready" })],
280- record: expect.objectContaining({
281- roomId: second.roomId,
282- sessionKey: "agent:main:second",
283- channel: "browser",
284- target: "host:local",
285-}),
319+});
320+expectEventFields(requireEvents(secondJoin), 0, { type: "session.ready" });
321+expectFields(secondJoinRecord.record, "second joined record", {
322+roomId: second.roomId,
323+sessionKey: "agent:main:second",
324+channel: "browser",
325+target: "host:local",
286326});
287327});
288328});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。