diff --git a/extensions/discord/src/directory-live.test.ts b/extensions/discord/src/directory-live.test.ts index 8ba3bc52c4a..afc0fd94170 100644 --- a/extensions/discord/src/directory-live.test.ts +++ b/extensions/discord/src/directory-live.test.ts @@ -1,74 +1,72 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { DirectoryConfigParams } from "../../../src/channels/plugins/directory-config.js"; - -const mocks = vi.hoisted(() => ({ - fetchDiscord: vi.fn(), - normalizeDiscordToken: vi.fn((token: string) => token.trim()), - resolveDiscordAccount: vi.fn(), -})); - -vi.mock("./accounts.js", () => ({ - resolveDiscordAccount: mocks.resolveDiscordAccount, -})); - -vi.mock("./api.js", () => ({ - fetchDiscord: mocks.fetchDiscord, -})); - -vi.mock("./token.js", () => ({ - normalizeDiscordToken: mocks.normalizeDiscordToken, -})); - +import type { OpenClawConfig } from "../../../src/config/config.js"; import { listDiscordDirectoryGroupsLive, listDiscordDirectoryPeersLive } from "./directory-live.js"; function makeParams(overrides: Partial = {}): DirectoryConfigParams { return { - cfg: {} as DirectoryConfigParams["cfg"], + cfg: { + channels: { + discord: { + token: "test-token", + }, + }, + } as OpenClawConfig, + accountId: "default", ...overrides, }; } +function jsonResponse(value: unknown): Response { + return new Response(JSON.stringify(value), { + status: 200, + headers: { "content-type": "application/json" }, + }); +} + describe("discord directory live lookups", () => { beforeEach(() => { - vi.clearAllMocks(); - mocks.resolveDiscordAccount.mockReturnValue({ token: "test-token" }); - mocks.normalizeDiscordToken.mockImplementation((token: string) => token.trim()); + vi.restoreAllMocks(); }); it("returns empty group directory when token is missing", async () => { - mocks.normalizeDiscordToken.mockReturnValue(""); - - const rows = await listDiscordDirectoryGroupsLive(makeParams({ query: "general" })); + const rows = await listDiscordDirectoryGroupsLive({ + ...makeParams(), + cfg: { channels: { discord: { token: "" } } } as OpenClawConfig, + query: "general", + }); expect(rows).toEqual([]); - expect(mocks.fetchDiscord).not.toHaveBeenCalled(); }); it("returns empty peer directory without query and skips guild listing", async () => { + const fetchSpy = vi.spyOn(globalThis, "fetch"); + const rows = await listDiscordDirectoryPeersLive(makeParams({ query: " " })); expect(rows).toEqual([]); - expect(mocks.fetchDiscord).not.toHaveBeenCalled(); + expect(fetchSpy).not.toHaveBeenCalled(); }); it("filters group channels by query and respects limit", async () => { - mocks.fetchDiscord.mockImplementation(async (path: string) => { - if (path === "/users/@me/guilds") { - return [ + vi.spyOn(globalThis, "fetch").mockImplementation(async (input) => { + const url = String(input); + if (url.endsWith("/users/@me/guilds")) { + return jsonResponse([ { id: "g1", name: "Guild 1" }, { id: "g2", name: "Guild 2" }, - ]; + ]); } - if (path === "/guilds/g1/channels") { - return [ + if (url.endsWith("/guilds/g1/channels")) { + return jsonResponse([ { id: "c1", name: "general" }, { id: "c2", name: "random" }, - ]; + ]); } - if (path === "/guilds/g2/channels") { - return [{ id: "c3", name: "announcements" }]; + if (url.endsWith("/guilds/g2/channels")) { + return jsonResponse([{ id: "c3", name: "announcements" }]); } - return []; + return jsonResponse([]); }); const rows = await listDiscordDirectoryGroupsLive(makeParams({ query: "an", limit: 2 })); @@ -80,21 +78,22 @@ describe("discord directory live lookups", () => { }); it("returns ranked peer results and caps member search by limit", async () => { - mocks.fetchDiscord.mockImplementation(async (path: string) => { - if (path === "/users/@me/guilds") { - return [{ id: "g1", name: "Guild 1" }]; + vi.spyOn(globalThis, "fetch").mockImplementation(async (input) => { + const url = String(input); + if (url.endsWith("/users/@me/guilds")) { + return jsonResponse([{ id: "g1", name: "Guild 1" }]); } - if (path.startsWith("/guilds/g1/members/search?")) { - const params = new URLSearchParams(path.split("?")[1] ?? ""); + if (url.includes("/guilds/g1/members/search?")) { + const params = new URL(url).searchParams; expect(params.get("query")).toBe("alice"); expect(params.get("limit")).toBe("2"); - return [ + return jsonResponse([ { user: { id: "u1", username: "alice", bot: false }, nick: "Ali" }, { user: { id: "u2", username: "alice-bot", bot: true }, nick: null }, { user: { id: "u3", username: "ignored", bot: false }, nick: null }, - ]; + ]); } - return []; + return jsonResponse([]); }); const rows = await listDiscordDirectoryPeersLive(makeParams({ query: "alice", limit: 2 })); diff --git a/extensions/discord/src/targets.test.ts b/extensions/discord/src/targets.test.ts index 527e0164ba8..fa8b739b3b5 100644 --- a/extensions/discord/src/targets.test.ts +++ b/extensions/discord/src/targets.test.ts @@ -1,13 +1,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../../src/config/config.js"; -import { listDiscordDirectoryPeersLive } from "./directory-live.js"; +import * as directoryLive from "./directory-live.js"; import { normalizeDiscordMessagingTarget } from "./normalize.js"; import { parseDiscordTarget, resolveDiscordChannelId, resolveDiscordTarget } from "./targets.js"; -vi.mock("./directory-live.js", () => ({ - listDiscordDirectoryPeersLive: vi.fn(), -})); - describe("parseDiscordTarget", () => { it("parses user mention and prefixes", () => { const cases = [ @@ -73,14 +69,15 @@ describe("resolveDiscordChannelId", () => { describe("resolveDiscordTarget", () => { const cfg = { channels: { discord: {} } } as OpenClawConfig; - const listPeers = vi.mocked(listDiscordDirectoryPeersLive); beforeEach(() => { - listPeers.mockClear(); + vi.restoreAllMocks(); }); it("returns a resolved user for usernames", async () => { - listPeers.mockResolvedValueOnce([{ kind: "user", id: "user:999", name: "Jane" } as const]); + vi.spyOn(directoryLive, "listDiscordDirectoryPeersLive").mockResolvedValueOnce([ + { kind: "user", id: "user:999", name: "Jane" } as const, + ]); await expect( resolveDiscordTarget("jane", { cfg, accountId: "default" }), @@ -88,14 +85,14 @@ describe("resolveDiscordTarget", () => { }); it("falls back to parsing when lookup misses", async () => { - listPeers.mockResolvedValueOnce([]); + vi.spyOn(directoryLive, "listDiscordDirectoryPeersLive").mockResolvedValueOnce([]); await expect( resolveDiscordTarget("general", { cfg, accountId: "default" }), ).resolves.toMatchObject({ kind: "channel", id: "general" }); }); it("does not call directory lookup for explicit user ids", async () => { - listPeers.mockResolvedValueOnce([]); + const listPeers = vi.spyOn(directoryLive, "listDiscordDirectoryPeersLive"); await expect( resolveDiscordTarget("user:123", { cfg, accountId: "default" }), ).resolves.toMatchObject({ kind: "user", id: "123" });