






























@@ -1,7 +1,8 @@
11import { Agent, createServer, request } from "node:http";
2-import { describe, expect, it } from "vitest";
3-import { closeQaHttpServer, handleQaBusRequest } from "./bus-server.js";
2+import { afterEach, describe, expect, it } from "vitest";
3+import { closeQaHttpServer, handleQaBusRequest, startQaBusServer } from "./bus-server.js";
44import { createQaBusState } from "./bus-state.js";
5+import type { QaBusPollResult } from "./runtime-api.js";
5667async function listenOnLoopback(server: ReturnType<typeof createServer>): Promise<number> {
78await new Promise<void>((resolve, reject) => {
@@ -35,6 +36,29 @@ async function requestOnce(params: { port: number; agent: Agent }): Promise<void
3536});
3637}
373839+async function pollQaBus(params: {
40+baseUrl: string;
41+accountId: string;
42+cursor: number;
43+timeoutMs: number;
44+}): Promise<QaBusPollResult> {
45+const response = await fetch(`${params.baseUrl}/v1/poll`, {
46+method: "POST",
47+headers: {
48+"content-type": "application/json",
49+},
50+body: JSON.stringify({
51+accountId: params.accountId,
52+cursor: params.cursor,
53+timeoutMs: params.timeoutMs,
54+}),
55+});
56+if (!response.ok) {
57+throw new Error(`qa-bus request failed: ${response.status}`);
58+}
59+return (await response.json()) as QaBusPollResult;
60+}
61+3862describe("closeQaHttpServer", () => {
3963it("closes idle keep-alive sockets so suite processes can exit", async () => {
4064const server = createServer((_req, res) => {
@@ -59,6 +83,54 @@ describe("closeQaHttpServer", () => {
5983});
6084});
618586+describe("qa-bus server", () => {
87+const stops: Array<() => Promise<void>> = [];
88+89+afterEach(async () => {
90+await Promise.all(stops.splice(0).map((stop) => stop()));
91+});
92+93+it("wakes stale-cursor long polls as soon as matching account traffic arrives", async () => {
94+const state = createQaBusState();
95+const bus = await startQaBusServer({ state });
96+stops.push(bus.stop);
97+98+const pending = pollQaBus({
99+baseUrl: bus.baseUrl,
100+accountId: "acct-a",
101+cursor: 999,
102+timeoutMs: 500,
103+});
104+105+setTimeout(() => {
106+state.addInboundMessage({
107+accountId: "acct-a",
108+conversation: { id: "target", kind: "direct" },
109+senderId: "acct-a-user",
110+text: "fresh event",
111+});
112+}, 20);
113+114+const result = await Promise.race([
115+pending,
116+new Promise<"timed-out">((resolve) => {
117+setTimeout(() => resolve("timed-out"), 150);
118+}),
119+]);
120+121+expect(result).not.toBe("timed-out");
122+if (result === "timed-out") {
123+throw new Error("stale-cursor long poll did not wake before timeout window");
124+}
125+expect(result.events).toHaveLength(1);
126+expect(result.events[0]).toMatchObject({
127+accountId: "acct-a",
128+cursor: 1,
129+kind: "inbound-message",
130+});
131+});
132+});
133+62134describe("handleQaBusRequest", () => {
63135it("returns a controlled error when a v1 POST body exceeds the limit", async () => {
64136const req = {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。