Files
openclaw/extensions/whatsapp/src/socket-close.test.ts
Peter Steinberger bf413374cc fix(whatsapp): add live group directory (#109886)
* fix(whatsapp): serve live group directory safely

Co-authored-by: xialonglee <li.xialong@xydigit.com>

* fix(whatsapp): complete owner lock retry policy

* test(whatsapp): match socket end signature

* fix(whatsapp): preserve live lookup cleanup errors

* refactor(whatsapp): keep owner error code internal

---------

Co-authored-by: xialonglee <li.xialong@xydigit.com>
2026-07-17 11:12:07 +01:00

97 lines
2.9 KiB
TypeScript

// Whatsapp tests cover transport-confirmed Baileys socket shutdown.
import { EventEmitter } from "node:events";
import { describe, expect, it, vi } from "vitest";
import { closeWhatsAppSocketAndWait } from "./socket-close.js";
describe("closeWhatsAppSocketAndWait", () => {
it("closes the underlying transport when Baileys end resolves while it remains open", async () => {
let closed = false;
const sock = {
end: vi.fn().mockResolvedValue(undefined),
ws: {
close: vi.fn(async () => {
closed = true;
}),
get isClosed() {
return closed;
},
},
};
await closeWhatsAppSocketAndWait(sock as never, "test close");
expect(sock.end).toHaveBeenCalledOnce();
expect(sock.ws.close).toHaveBeenCalledOnce();
expect(sock.ws.isClosed).toBe(true);
});
it("waits for the asynchronous WebSocket close handshake", async () => {
let closed = false;
let closing = false;
const ws = new EventEmitter() as EventEmitter & {
close: ReturnType<typeof vi.fn>;
readonly isClosed: boolean;
readonly isClosing: boolean;
};
Object.defineProperties(ws, {
isClosed: { get: () => closed },
isClosing: { get: () => closing },
});
ws.close = vi.fn(() => {
closing = true;
setImmediate(() => {
closed = true;
closing = false;
ws.emit("close");
});
});
const sock = { end: vi.fn(), ws };
await closeWhatsAppSocketAndWait(sock as never, "test close");
expect(sock.ws.close).toHaveBeenCalledOnce();
expect(sock.ws.isClosed).toBe(true);
});
it("bounds Baileys teardown after the transport is already closed", async () => {
vi.useFakeTimers();
const ws = new EventEmitter() as EventEmitter & {
close: ReturnType<typeof vi.fn>;
isClosed: boolean;
isClosing: boolean;
};
ws.close = vi.fn();
ws.isClosed = true;
ws.isClosing = false;
const sock = { end: vi.fn(() => new Promise<void>(() => {})), ws };
try {
const closeTask = closeWhatsAppSocketAndWait(sock as never, "test close");
await vi.advanceTimersByTimeAsync(15_000);
await expect(closeTask).resolves.toBeUndefined();
expect(ws.close).not.toHaveBeenCalled();
} finally {
vi.useRealTimers();
}
});
it("rejects when neither Baileys nor the transport confirms closure", async () => {
const ws = new EventEmitter() as EventEmitter & {
close: ReturnType<typeof vi.fn>;
isClosed: boolean;
isClosing: boolean;
};
ws.close = vi.fn().mockRejectedValue(new Error("close failed"));
ws.isClosed = false;
ws.isClosing = false;
const sock = {
end: vi.fn().mockResolvedValue(undefined),
ws,
};
await expect(closeWhatsAppSocketAndWait(sock as never, "test close")).rejects.toThrow(
"socket close could not be confirmed",
);
});
});