test(ringcentral): add unit tests for targets, accounts, and monitor

- targets.test.ts: tests for target normalization and parsing
- accounts.test.ts: tests for credential resolution and account management
- monitor.test.ts: tests for sender allowlist matching

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
John Lin 2026-01-28 10:51:50 +08:00
parent 0b1aebe80f
commit ab952fd8cd
No known key found for this signature in database
3 changed files with 456 additions and 0 deletions

View File

@ -0,0 +1,291 @@
import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
import type { ClawdbotConfig } from "clawdbot/plugin-sdk";
import {
listRingCentralAccountIds,
resolveDefaultRingCentralAccountId,
resolveRingCentralAccount,
listEnabledRingCentralAccounts,
} from "./accounts.js";
describe("listRingCentralAccountIds", () => {
it("returns default account when no accounts configured", () => {
const cfg = { channels: {} } as ClawdbotConfig;
expect(listRingCentralAccountIds(cfg)).toEqual(["default"]);
});
it("returns default account when ringcentral channel not configured", () => {
const cfg = { channels: { telegram: { enabled: true } } } as ClawdbotConfig;
expect(listRingCentralAccountIds(cfg)).toEqual(["default"]);
});
it("returns configured account IDs sorted alphabetically", () => {
const cfg = {
channels: {
ringcentral: {
accounts: {
work: { enabled: true },
personal: { enabled: true },
},
},
},
} as ClawdbotConfig;
expect(listRingCentralAccountIds(cfg)).toEqual(["personal", "work"]);
});
});
describe("resolveDefaultRingCentralAccountId", () => {
it("returns explicitly set default account", () => {
const cfg = {
channels: {
ringcentral: {
defaultAccount: "work",
accounts: {
work: {},
personal: {},
},
},
},
} as ClawdbotConfig;
expect(resolveDefaultRingCentralAccountId(cfg)).toBe("work");
});
it("returns default if included in accounts", () => {
const cfg = {
channels: {
ringcentral: {
accounts: {
default: {},
other: {},
},
},
},
} as ClawdbotConfig;
expect(resolveDefaultRingCentralAccountId(cfg)).toBe("default");
});
it("returns first account ID when no default specified", () => {
const cfg = {
channels: {
ringcentral: {
accounts: {
zebra: {},
alpha: {},
},
},
},
} as ClawdbotConfig;
expect(resolveDefaultRingCentralAccountId(cfg)).toBe("alpha");
});
});
describe("resolveRingCentralAccount", () => {
const originalEnv = process.env;
beforeEach(() => {
vi.resetModules();
process.env = { ...originalEnv };
});
afterEach(() => {
process.env = originalEnv;
});
it("resolves account from config credentials", () => {
const cfg = {
channels: {
ringcentral: {
enabled: true,
clientId: "test-client-id",
clientSecret: "test-client-secret",
jwt: "test-jwt",
server: "https://platform.devtest.ringcentral.com",
},
},
} as ClawdbotConfig;
const account = resolveRingCentralAccount({ cfg });
expect(account.accountId).toBe("default");
expect(account.enabled).toBe(true);
expect(account.credentialSource).toBe("config");
expect(account.clientId).toBe("test-client-id");
expect(account.clientSecret).toBe("test-client-secret");
expect(account.jwt).toBe("test-jwt");
expect(account.server).toBe("https://platform.devtest.ringcentral.com");
});
it("resolves account from environment variables", () => {
process.env.RINGCENTRAL_CLIENT_ID = "env-client-id";
process.env.RINGCENTRAL_CLIENT_SECRET = "env-client-secret";
process.env.RINGCENTRAL_JWT = "env-jwt";
process.env.RINGCENTRAL_SERVER = "https://platform.devtest.ringcentral.com";
const cfg = {
channels: {
ringcentral: {
enabled: true,
},
},
} as ClawdbotConfig;
const account = resolveRingCentralAccount({ cfg });
expect(account.credentialSource).toBe("env");
expect(account.clientId).toBe("env-client-id");
expect(account.clientSecret).toBe("env-client-secret");
expect(account.jwt).toBe("env-jwt");
expect(account.server).toBe("https://platform.devtest.ringcentral.com");
});
it("returns none source when no credentials configured", () => {
const cfg = {
channels: {
ringcentral: {
enabled: true,
},
},
} as ClawdbotConfig;
const account = resolveRingCentralAccount({ cfg });
expect(account.credentialSource).toBe("none");
expect(account.clientId).toBeUndefined();
});
it("uses default server when not specified", () => {
const cfg = {
channels: {
ringcentral: {
enabled: true,
clientId: "test-client-id",
clientSecret: "test-client-secret",
jwt: "test-jwt",
},
},
} as ClawdbotConfig;
const account = resolveRingCentralAccount({ cfg });
expect(account.server).toBe("https://platform.ringcentral.com");
});
it("resolves specific account from accounts map", () => {
const cfg = {
channels: {
ringcentral: {
enabled: true,
accounts: {
work: {
clientId: "work-client-id",
clientSecret: "work-client-secret",
jwt: "work-jwt",
name: "Work Account",
},
},
},
},
} as ClawdbotConfig;
const account = resolveRingCentralAccount({ cfg, accountId: "work" });
expect(account.accountId).toBe("work");
expect(account.name).toBe("Work Account");
expect(account.clientId).toBe("work-client-id");
});
it("merges base config with account config", () => {
const cfg = {
channels: {
ringcentral: {
enabled: true,
server: "https://base.server.com",
accounts: {
work: {
clientId: "work-client-id",
clientSecret: "work-client-secret",
jwt: "work-jwt",
},
},
},
},
} as ClawdbotConfig;
const account = resolveRingCentralAccount({ cfg, accountId: "work" });
expect(account.server).toBe("https://base.server.com");
});
it("account config overrides base config", () => {
const cfg = {
channels: {
ringcentral: {
enabled: true,
server: "https://base.server.com",
accounts: {
work: {
clientId: "work-client-id",
clientSecret: "work-client-secret",
jwt: "work-jwt",
server: "https://work.server.com",
},
},
},
},
} as ClawdbotConfig;
const account = resolveRingCentralAccount({ cfg, accountId: "work" });
expect(account.server).toBe("https://work.server.com");
});
});
describe("listEnabledRingCentralAccounts", () => {
it("returns only enabled accounts", () => {
const cfg = {
channels: {
ringcentral: {
enabled: true,
clientId: "base-id",
clientSecret: "base-secret",
jwt: "base-jwt",
accounts: {
enabled1: {
enabled: true,
clientId: "e1-id",
clientSecret: "e1-secret",
jwt: "e1-jwt",
},
disabled1: {
enabled: false,
clientId: "d1-id",
clientSecret: "d1-secret",
jwt: "d1-jwt",
},
},
},
},
} as ClawdbotConfig;
const accounts = listEnabledRingCentralAccounts(cfg);
expect(accounts.map((a) => a.accountId)).toContain("enabled1");
expect(accounts.map((a) => a.accountId)).not.toContain("disabled1");
});
it("returns empty array when channel is disabled", () => {
const cfg = {
channels: {
ringcentral: {
enabled: false,
accounts: {
work: { enabled: true },
},
},
},
} as ClawdbotConfig;
const accounts = listEnabledRingCentralAccounts(cfg);
expect(accounts).toEqual([]);
});
});

View File

@ -0,0 +1,47 @@
import { describe, expect, it } from "vitest";
import { isSenderAllowed } from "./monitor.js";
describe("isSenderAllowed", () => {
it("returns true when allowFrom contains wildcard", () => {
expect(isSenderAllowed("12345", ["*"])).toBe(true);
expect(isSenderAllowed("any-user", ["*"])).toBe(true);
});
it("returns true when sender ID matches exactly", () => {
expect(isSenderAllowed("12345", ["12345"])).toBe(true);
expect(isSenderAllowed("12345", ["other", "12345"])).toBe(true);
});
it("returns true when sender ID matches with ringcentral: prefix", () => {
expect(isSenderAllowed("12345", ["ringcentral:12345"])).toBe(true);
expect(isSenderAllowed("12345", ["RINGCENTRAL:12345"])).toBe(true);
});
it("returns true when sender ID matches with rc: prefix", () => {
expect(isSenderAllowed("12345", ["rc:12345"])).toBe(true);
expect(isSenderAllowed("12345", ["RC:12345"])).toBe(true);
});
it("returns true when sender ID matches with user: prefix", () => {
expect(isSenderAllowed("12345", ["user:12345"])).toBe(true);
expect(isSenderAllowed("12345", ["USER:12345"])).toBe(true);
});
it("returns false when sender ID not in allowFrom", () => {
expect(isSenderAllowed("12345", ["67890"])).toBe(false);
expect(isSenderAllowed("12345", [])).toBe(false);
});
it("handles case-insensitive matching", () => {
expect(isSenderAllowed("ABC123", ["abc123"])).toBe(true);
expect(isSenderAllowed("abc123", ["ABC123"])).toBe(true);
});
it("handles whitespace in allowFrom entries", () => {
expect(isSenderAllowed("12345", [" 12345 "])).toBe(true);
});
it("ignores empty entries in allowFrom", () => {
expect(isSenderAllowed("12345", ["", "12345", " "])).toBe(true);
});
});

View File

@ -0,0 +1,118 @@
import { describe, expect, it } from "vitest";
import {
normalizeRingCentralTarget,
isRingCentralChatTarget,
isRingCentralUserTarget,
formatRingCentralChatTarget,
formatRingCentralUserTarget,
parseRingCentralTarget,
} from "./targets.js";
describe("normalizeRingCentralTarget", () => {
it("returns null for empty string", () => {
expect(normalizeRingCentralTarget("")).toBeNull();
expect(normalizeRingCentralTarget(" ")).toBeNull();
});
it("removes ringcentral: prefix", () => {
expect(normalizeRingCentralTarget("ringcentral:12345")).toBe("12345");
expect(normalizeRingCentralTarget("RINGCENTRAL:12345")).toBe("12345");
});
it("removes rc: prefix", () => {
expect(normalizeRingCentralTarget("rc:12345")).toBe("12345");
expect(normalizeRingCentralTarget("RC:12345")).toBe("12345");
});
it("removes chat: prefix", () => {
expect(normalizeRingCentralTarget("chat:12345")).toBe("12345");
});
it("removes user: prefix", () => {
expect(normalizeRingCentralTarget("user:12345")).toBe("12345");
});
it("removes combined prefixes", () => {
expect(normalizeRingCentralTarget("rc:chat:12345")).toBe("12345");
expect(normalizeRingCentralTarget("ringcentral:user:12345")).toBe("12345");
});
it("trims whitespace", () => {
expect(normalizeRingCentralTarget(" 12345 ")).toBe("12345");
});
it("returns plain ID as-is", () => {
expect(normalizeRingCentralTarget("12345")).toBe("12345");
});
});
describe("isRingCentralChatTarget", () => {
it("returns true for numeric IDs", () => {
expect(isRingCentralChatTarget("12345")).toBe(true);
expect(isRingCentralChatTarget("rc:12345")).toBe(true);
expect(isRingCentralChatTarget("chat:12345")).toBe(true);
});
it("returns false for non-numeric IDs", () => {
expect(isRingCentralChatTarget("abc")).toBe(false);
expect(isRingCentralChatTarget("12345abc")).toBe(false);
});
it("returns false for empty string", () => {
expect(isRingCentralChatTarget("")).toBe(false);
});
});
describe("isRingCentralUserTarget", () => {
it("returns true for numeric IDs", () => {
expect(isRingCentralUserTarget("12345")).toBe(true);
expect(isRingCentralUserTarget("rc:12345")).toBe(true);
});
it("returns false for empty string", () => {
expect(isRingCentralUserTarget("")).toBe(false);
});
});
describe("formatRingCentralChatTarget", () => {
it("formats chat ID with rc:chat: prefix", () => {
expect(formatRingCentralChatTarget("12345")).toBe("rc:chat:12345");
});
});
describe("formatRingCentralUserTarget", () => {
it("formats user ID with rc:user: prefix", () => {
expect(formatRingCentralUserTarget("12345")).toBe("rc:user:12345");
});
});
describe("parseRingCentralTarget", () => {
it("parses chat: prefix", () => {
expect(parseRingCentralTarget("chat:12345")).toEqual({ type: "chat", id: "12345" });
expect(parseRingCentralTarget("rc:chat:12345")).toEqual({ type: "chat", id: "12345" });
expect(parseRingCentralTarget("ringcentral:chat:12345")).toEqual({ type: "chat", id: "12345" });
});
it("parses user: prefix", () => {
expect(parseRingCentralTarget("user:12345")).toEqual({ type: "user", id: "12345" });
expect(parseRingCentralTarget("rc:user:12345")).toEqual({ type: "user", id: "12345" });
});
it("parses group: prefix as chat", () => {
expect(parseRingCentralTarget("group:12345")).toEqual({ type: "chat", id: "12345" });
expect(parseRingCentralTarget("team:12345")).toEqual({ type: "chat", id: "12345" });
});
it("defaults numeric IDs to chat type", () => {
expect(parseRingCentralTarget("12345")).toEqual({ type: "chat", id: "12345" });
expect(parseRingCentralTarget("rc:12345")).toEqual({ type: "chat", id: "12345" });
});
it("returns unknown for non-numeric IDs without type prefix", () => {
expect(parseRingCentralTarget("abc")).toEqual({ type: "unknown", id: "abc" });
});
it("trims whitespace", () => {
expect(parseRingCentralTarget(" chat:12345 ")).toEqual({ type: "chat", id: "12345" });
});
});