mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
3bb04a551d
Co-authored-by: Peter Steinberger <steipete@gmail.com>
209 lines
6.5 KiB
TypeScript
209 lines
6.5 KiB
TypeScript
// Discord tests cover pluralkit plugin behavior.
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { fetchPluralKitMessageInfo } from "./pluralkit.js";
|
|
|
|
type MockResponse = {
|
|
status: number;
|
|
ok: boolean;
|
|
text: () => Promise<string>;
|
|
json: () => Promise<unknown>;
|
|
body: null;
|
|
arrayBuffer: () => Promise<Buffer>;
|
|
};
|
|
|
|
const buildResponse = (params: { status: number; body?: unknown }): MockResponse => {
|
|
const body = params.body;
|
|
const textPayload = typeof body === "string" ? body : body == null ? "" : JSON.stringify(body);
|
|
return {
|
|
status: params.status,
|
|
ok: params.status >= 200 && params.status < 300,
|
|
text: async () => textPayload,
|
|
json: async () => body ?? {},
|
|
body: null,
|
|
arrayBuffer: async () => Buffer.from(textPayload),
|
|
};
|
|
};
|
|
|
|
function cancelTrackedResponse(
|
|
text: string,
|
|
init: ResponseInit,
|
|
): {
|
|
response: Response;
|
|
wasCanceled: () => boolean;
|
|
} {
|
|
let canceled = false;
|
|
const stream = new ReadableStream<Uint8Array>({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode(text));
|
|
},
|
|
cancel() {
|
|
canceled = true;
|
|
},
|
|
});
|
|
return {
|
|
response: new Response(stream, init),
|
|
wasCanceled: () => canceled,
|
|
};
|
|
}
|
|
|
|
describe("fetchPluralKitMessageInfo", () => {
|
|
it("returns null when disabled", async () => {
|
|
const fetcher = vi.fn();
|
|
const result = await fetchPluralKitMessageInfo({
|
|
messageId: "123",
|
|
config: { enabled: false },
|
|
fetcher: fetcher as unknown as typeof fetch,
|
|
});
|
|
expect(result).toBeNull();
|
|
expect(fetcher).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns null on 404", async () => {
|
|
const tracked = cancelTrackedResponse("missing", { status: 404 });
|
|
const fetcher = vi.fn(async () => tracked.response);
|
|
const result = await fetchPluralKitMessageInfo({
|
|
messageId: "missing",
|
|
config: { enabled: true },
|
|
fetcher: fetcher as unknown as typeof fetch,
|
|
});
|
|
expect(result).toBeNull();
|
|
expect(tracked.wasCanceled()).toBe(true);
|
|
});
|
|
|
|
it("returns payload and sends token when configured", async () => {
|
|
let receivedHeaders: Record<string, string> | undefined;
|
|
const fetcher = vi.fn(async (_url: string, init?: RequestInit) => {
|
|
receivedHeaders = init?.headers as Record<string, string> | undefined;
|
|
return buildResponse({
|
|
status: 200,
|
|
body: {
|
|
id: "123",
|
|
member: { id: "mem_1", name: "Alex" },
|
|
system: { id: "sys_1", name: "System" },
|
|
},
|
|
});
|
|
});
|
|
|
|
const result = await fetchPluralKitMessageInfo({
|
|
messageId: "123",
|
|
config: { enabled: true, token: "pk_test" },
|
|
fetcher: fetcher as unknown as typeof fetch,
|
|
});
|
|
|
|
expect(result?.member?.id).toBe("mem_1");
|
|
expect(receivedHeaders?.Authorization).toBe("pk_test");
|
|
});
|
|
|
|
it.each([
|
|
["null", "null"],
|
|
["array", "[]"],
|
|
])(
|
|
"rejects a %s PluralKit message envelope with a stable provider error",
|
|
async (_kind, body) => {
|
|
const fetcher = vi.fn(async () => buildResponse({ status: 200, body }));
|
|
await expect(
|
|
fetchPluralKitMessageInfo({
|
|
messageId: "123",
|
|
config: { enabled: true },
|
|
fetcher: fetcher as unknown as typeof fetch,
|
|
}),
|
|
).rejects.toThrow("PluralKit message: malformed JSON response");
|
|
},
|
|
);
|
|
|
|
it("aborts PluralKit response body reads that exceed the lookup timeout", async () => {
|
|
vi.useFakeTimers();
|
|
try {
|
|
let observedSignal: AbortSignal | undefined;
|
|
const fetcher = vi.fn<typeof fetch>(async (_url, init) => {
|
|
observedSignal = init?.signal ?? undefined;
|
|
const body = new ReadableStream<Uint8Array>({
|
|
start(controller) {
|
|
observedSignal?.addEventListener(
|
|
"abort",
|
|
() => controller.error(new DOMException("PluralKit lookup timed out", "AbortError")),
|
|
{ once: true },
|
|
);
|
|
},
|
|
});
|
|
return new Response(body, {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
});
|
|
|
|
const lookupPromise = fetchPluralKitMessageInfo({
|
|
messageId: "slow-body",
|
|
config: { enabled: true },
|
|
fetcher,
|
|
});
|
|
|
|
await vi.advanceTimersByTimeAsync(0);
|
|
expect(fetcher).toHaveBeenCalledOnce();
|
|
expect(observedSignal?.aborted).toBe(false);
|
|
const lookupRejection = expect(lookupPromise).rejects.toThrow(/timed out|abort/i);
|
|
|
|
await vi.advanceTimersByTimeAsync(10_000);
|
|
await lookupRejection;
|
|
expect(observedSignal?.aborted).toBe(true);
|
|
} finally {
|
|
vi.useRealTimers();
|
|
}
|
|
});
|
|
|
|
it("relays parent cancellation to the PluralKit request", async () => {
|
|
const parent = new AbortController();
|
|
let observedSignal: AbortSignal | undefined;
|
|
const fetcher = vi.fn<typeof fetch>(async (_url, init) => {
|
|
observedSignal = init?.signal ?? undefined;
|
|
return await new Promise<Response>((_resolve, reject) => {
|
|
observedSignal?.addEventListener(
|
|
"abort",
|
|
() => {
|
|
const reason = observedSignal?.reason;
|
|
reject(reason instanceof Error ? reason : new Error("PluralKit request aborted"));
|
|
},
|
|
{ once: true },
|
|
);
|
|
});
|
|
});
|
|
|
|
const lookupPromise = fetchPluralKitMessageInfo({
|
|
messageId: "cancelled",
|
|
config: { enabled: true },
|
|
fetcher,
|
|
signal: parent.signal,
|
|
});
|
|
parent.abort(new Error("preflight stopped"));
|
|
|
|
await expect(lookupPromise).rejects.toThrow("preflight stopped");
|
|
expect(observedSignal?.aborted).toBe(true);
|
|
});
|
|
|
|
it("bounds PluralKit API error bodies without using response.text()", async () => {
|
|
const tracked = cancelTrackedResponse(`${"plural failure ".repeat(1024)}tail`, {
|
|
status: 500,
|
|
headers: { "content-type": "text/plain" },
|
|
});
|
|
const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded"));
|
|
const fetcher = vi.fn(async () => tracked.response);
|
|
|
|
let caught: Error | undefined;
|
|
try {
|
|
await fetchPluralKitMessageInfo({
|
|
messageId: "boom",
|
|
config: { enabled: true },
|
|
fetcher: fetcher as unknown as typeof fetch,
|
|
});
|
|
} catch (error) {
|
|
caught = error as Error;
|
|
}
|
|
|
|
expect(caught?.message).toContain("PluralKit API failed (500): plural failure");
|
|
expect(caught?.message).not.toContain("tail");
|
|
expect(caught?.message.length).toBeLessThan(8_400);
|
|
expect(tracked.wasCanceled()).toBe(true);
|
|
expect(textSpy).not.toHaveBeenCalled();
|
|
});
|
|
});
|