






















@@ -113,6 +113,14 @@ vi.mock("./session-history-state.js", () => ({
113113114114import { handleSessionHistoryHttpRequest } from "./sessions-history-http.js";
115115116+const SESSION_HISTORY_URL = "/sessions/agent%3Amain/history";
117+const SESSION_FILE = "/tmp/session-1.jsonl";
118+const TRUSTED_PROXY_STARTUP_OPTIONS = {
119+auth: { mode: "trusted-proxy" } as never,
120+trustedProxies: ["10.0.0.1"],
121+allowRealIpFallback: false,
122+} satisfies Parameters<typeof handleSessionHistoryHttpRequest>[2];
123+116124class MockReq extends EventEmitter {
117125url: string;
118126method: string;
@@ -161,6 +169,51 @@ class MockRes extends EventEmitter {
161169flushHeaders() {}
162170}
163171172+async function openSessionHistoryStream(
173+options: Parameters<typeof handleSessionHistoryHttpRequest>[2],
174+) {
175+const req = new MockReq(SESSION_HISTORY_URL);
176+const res = new MockRes();
177+178+const handled = await handleSessionHistoryHttpRequest(
179+req as unknown as IncomingMessage,
180+res as unknown as ServerResponse,
181+options,
182+);
183+184+expect(handled).toBe(true);
185+expect(transcriptUpdateHandler).toBeTypeOf("function");
186+187+return res;
188+}
189+190+function emitTranscriptTextUpdate({
191+ sessionFile = SESSION_FILE,
192+ text,
193+ messageId,
194+}: {
195+sessionFile?: string;
196+text: string;
197+messageId: string;
198+}) {
199+transcriptUpdateHandler?.({
200+ sessionFile,
201+message: { role: "assistant", content: [{ type: "text", text }] },
202+ messageId,
203+});
204+}
205+206+async function expectStreamClosedWithoutMessage(res: MockRes, text: string) {
207+await vi.waitFor(() => {
208+expect(res.writableEnded).toBe(true);
209+});
210+211+const joined = res.writes.join("");
212+expect(joined).not.toContain("event: message");
213+expect(joined).not.toContain(text);
214+expect(res.writableEnded).toBe(true);
215+}
216+164217afterEach(() => {
165218transcriptUpdateHandler = undefined;
166219authRevoked = false;
@@ -173,95 +226,42 @@ afterEach(() => {
173226174227describe("session history SSE auth revocation", () => {
175228it("closes the stream before delivering transcript updates after auth is revoked", async () => {
176-const req = new MockReq("/sessions/agent%3Amain/history");
177-const res = new MockRes();
229+const res = await openSessionHistoryStream({ auth: { mode: "trusted-proxy" } as never });
178230179-const handled = await handleSessionHistoryHttpRequest(
180-req as unknown as IncomingMessage,
181-res as unknown as ServerResponse,
182-{ auth: { mode: "trusted-proxy" } as never },
183-);
184-185-expect(handled).toBe(true);
186-expect(transcriptUpdateHandler).toBeTypeOf("function");
187231expect(res.headers.get("content-type")).toContain("text/event-stream");
188232189233authRevoked = true;
190234191-transcriptUpdateHandler?.({
192-sessionFile: "/tmp/session-1.jsonl",
193-message: { role: "assistant", content: [{ type: "text", text: "post-revocation secret" }] },
235+emitTranscriptTextUpdate({
236+text: "post-revocation secret",
194237messageId: "m-1",
195238});
196239197-await vi.waitFor(() => {
198-expect(res.writableEnded).toBe(true);
199-});
200-201-const joined = res.writes.join("");
202-expect(joined).not.toContain("event: message");
203-expect(joined).not.toContain("post-revocation secret");
204-expect(res.writableEnded).toBe(true);
240+await expectStreamClosedWithoutMessage(res, "post-revocation secret");
205241});
206242207243it("rechecks SSE auth against live proxy config instead of startup fallbacks", async () => {
208-const req = new MockReq("/sessions/agent%3Amain/history");
209-const res = new MockRes();
210-211-const handled = await handleSessionHistoryHttpRequest(
212-req as unknown as IncomingMessage,
213-res as unknown as ServerResponse,
214-{
215-auth: { mode: "trusted-proxy" } as never,
216-trustedProxies: ["10.0.0.1"],
217-allowRealIpFallback: false,
218-},
219-);
220-221-expect(handled).toBe(true);
222-expect(transcriptUpdateHandler).toBeTypeOf("function");
244+const res = await openSessionHistoryStream(TRUSTED_PROXY_STARTUP_OPTIONS);
223245224246gatewayConfig = {};
225247226-transcriptUpdateHandler?.({
227-sessionFile: "/tmp/session-1.jsonl",
228-message: { role: "assistant", content: [{ type: "text", text: "stale-proxy event" }] },
248+emitTranscriptTextUpdate({
249+text: "stale-proxy event",
229250messageId: "m-2",
230251});
231252232-await vi.waitFor(() => {
233-expect(res.writableEnded).toBe(true);
234-});
235-236-const joined = res.writes.join("");
237-expect(joined).not.toContain("event: message");
238-expect(joined).not.toContain("stale-proxy event");
239-expect(res.writableEnded).toBe(true);
253+await expectStreamClosedWithoutMessage(res, "stale-proxy event");
240254});
241255242256it("skips SSE reauth for transcript updates outside this stream", async () => {
243-const req = new MockReq("/sessions/agent%3Amain/history");
244-const res = new MockRes();
245-246-const handled = await handleSessionHistoryHttpRequest(
247-req as unknown as IncomingMessage,
248-res as unknown as ServerResponse,
249-{
250-auth: { mode: "trusted-proxy" } as never,
251-trustedProxies: ["10.0.0.1"],
252-allowRealIpFallback: false,
253-},
254-);
255-256-expect(handled).toBe(true);
257-expect(transcriptUpdateHandler).toBeTypeOf("function");
257+const res = await openSessionHistoryStream(TRUSTED_PROXY_STARTUP_OPTIONS);
258258259259authCheckCalls = 0;
260260gatewayConfig = {};
261261262-transcriptUpdateHandler?.({
262+emitTranscriptTextUpdate({
263263sessionFile: "/tmp/other-session.jsonl",
264-message: { role: "assistant", content: [{ type: "text", text: "other session" }] },
264+text: "other session",
265265messageId: "m-3",
266266});
267267此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。