mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-01 04:11:03 +00:00
234 lines
7.6 KiB
TypeScript
234 lines
7.6 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import * as normalize from "./normalize-target.js";
|
|
|
|
vi.mock("./normalize-target.js");
|
|
vi.mock("openclaw/plugin-sdk/channel-feedback", () => ({
|
|
missingTargetError: (platform: string, format: string) => new Error(`${platform}: ${format}`),
|
|
}));
|
|
|
|
let resolveWhatsAppOutboundTarget: typeof import("./resolve-outbound-target.js").resolveWhatsAppOutboundTarget;
|
|
|
|
type ResolveParams = Parameters<typeof resolveWhatsAppOutboundTarget>[0];
|
|
const PRIMARY_TARGET = "+11234567890";
|
|
const SECONDARY_TARGET = "+19876543210";
|
|
|
|
function expectResolutionError(params: ResolveParams) {
|
|
const result = resolveWhatsAppOutboundTarget(params);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) {
|
|
throw new Error("expected resolution to fail");
|
|
}
|
|
expect(result.error.message).toContain("WhatsApp");
|
|
}
|
|
|
|
function expectResolutionOk(params: ResolveParams, expectedTarget: string) {
|
|
const result = resolveWhatsAppOutboundTarget(params);
|
|
expect(result).toEqual({ ok: true, to: expectedTarget });
|
|
}
|
|
|
|
function mockNormalizedDirectMessage(...values: Array<string | null>) {
|
|
const normalizeMock = vi.mocked(normalize.normalizeWhatsAppTarget);
|
|
for (const value of values) {
|
|
normalizeMock.mockReturnValueOnce(value);
|
|
}
|
|
vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(false);
|
|
}
|
|
|
|
function expectAllowedForTarget(params: {
|
|
allowFrom: ResolveParams["allowFrom"];
|
|
mode: ResolveParams["mode"];
|
|
to?: string;
|
|
}) {
|
|
const to = params.to ?? PRIMARY_TARGET;
|
|
expectResolutionOk(
|
|
{
|
|
to,
|
|
allowFrom: params.allowFrom,
|
|
mode: params.mode,
|
|
},
|
|
to,
|
|
);
|
|
}
|
|
|
|
function expectDeniedForTarget(params: {
|
|
allowFrom: ResolveParams["allowFrom"];
|
|
mode: ResolveParams["mode"];
|
|
to?: string;
|
|
}) {
|
|
expectResolutionError({
|
|
to: params.to ?? PRIMARY_TARGET,
|
|
allowFrom: params.allowFrom,
|
|
mode: params.mode,
|
|
});
|
|
}
|
|
|
|
describe("resolveWhatsAppOutboundTarget", () => {
|
|
beforeEach(async () => {
|
|
vi.resetModules();
|
|
vi.resetAllMocks();
|
|
({ resolveWhatsAppOutboundTarget } = await import("./resolve-outbound-target.js"));
|
|
});
|
|
|
|
describe("empty/missing to parameter", () => {
|
|
it.each([
|
|
["null", null],
|
|
["undefined", undefined],
|
|
["empty string", ""],
|
|
["whitespace only", " "],
|
|
])("returns error when to is %s", (_label, to) => {
|
|
expectResolutionError({ to, allowFrom: undefined, mode: undefined });
|
|
});
|
|
});
|
|
|
|
describe("normalization failures", () => {
|
|
it("returns error when normalizeWhatsAppTarget returns null/undefined", () => {
|
|
vi.mocked(normalize.normalizeWhatsAppTarget).mockReturnValueOnce(null);
|
|
expectResolutionError({
|
|
to: "+1234567890",
|
|
allowFrom: undefined,
|
|
mode: undefined,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("group JID handling", () => {
|
|
it("returns success for valid group JID regardless of mode", () => {
|
|
vi.mocked(normalize.normalizeWhatsAppTarget).mockReturnValueOnce("120363123456789@g.us");
|
|
vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(true);
|
|
|
|
expectResolutionOk(
|
|
{
|
|
to: "120363123456789@g.us",
|
|
allowFrom: undefined,
|
|
mode: "implicit",
|
|
},
|
|
"120363123456789@g.us",
|
|
);
|
|
});
|
|
|
|
it("returns success for group JID in heartbeat mode", () => {
|
|
vi.mocked(normalize.normalizeWhatsAppTarget).mockReturnValueOnce("120363999888777@g.us");
|
|
vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(true);
|
|
|
|
expectResolutionOk(
|
|
{
|
|
to: "120363999888777@g.us",
|
|
allowFrom: undefined,
|
|
mode: "heartbeat",
|
|
},
|
|
"120363999888777@g.us",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("implicit/heartbeat mode with allowList", () => {
|
|
it("allows message when wildcard is present", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET, PRIMARY_TARGET);
|
|
expectAllowedForTarget({ allowFrom: ["*"], mode: "implicit" });
|
|
});
|
|
|
|
it("allows message when allowList is empty", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET, PRIMARY_TARGET);
|
|
expectAllowedForTarget({ allowFrom: [], mode: "implicit" });
|
|
});
|
|
|
|
it("allows message when target is in allowList", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET, PRIMARY_TARGET);
|
|
expectAllowedForTarget({ allowFrom: [PRIMARY_TARGET], mode: "implicit" });
|
|
});
|
|
|
|
it("denies message when target is not in allowList", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET, SECONDARY_TARGET);
|
|
expectDeniedForTarget({ allowFrom: [SECONDARY_TARGET], mode: "implicit" });
|
|
});
|
|
|
|
it("handles mixed numeric and string allowList entries", () => {
|
|
vi.mocked(normalize.normalizeWhatsAppTarget)
|
|
.mockReturnValueOnce("+11234567890")
|
|
.mockReturnValueOnce("+11234567890")
|
|
.mockReturnValueOnce("+11234567890");
|
|
vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(false);
|
|
|
|
expectAllowedForTarget({
|
|
allowFrom: [1234567890, PRIMARY_TARGET],
|
|
mode: "implicit",
|
|
});
|
|
});
|
|
|
|
it("filters out invalid normalized entries from allowList", () => {
|
|
vi.mocked(normalize.normalizeWhatsAppTarget)
|
|
.mockReturnValueOnce(null)
|
|
.mockReturnValueOnce("+11234567890")
|
|
.mockReturnValueOnce("+11234567890");
|
|
vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(false);
|
|
|
|
expectAllowedForTarget({
|
|
allowFrom: ["invalid", PRIMARY_TARGET],
|
|
mode: "implicit",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("heartbeat mode", () => {
|
|
it("allows message when target is in allowList in heartbeat mode", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET, PRIMARY_TARGET);
|
|
expectAllowedForTarget({ allowFrom: [PRIMARY_TARGET], mode: "heartbeat" });
|
|
});
|
|
|
|
it("denies message when target is not in allowList in heartbeat mode", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET, SECONDARY_TARGET);
|
|
expectDeniedForTarget({ allowFrom: [SECONDARY_TARGET], mode: "heartbeat" });
|
|
});
|
|
});
|
|
|
|
describe("explicit/custom modes", () => {
|
|
it("allows message in null mode when allowList is not set", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET);
|
|
expectAllowedForTarget({ allowFrom: undefined, mode: null });
|
|
});
|
|
|
|
it("allows message in undefined mode when allowList is not set", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET);
|
|
expectAllowedForTarget({ allowFrom: undefined, mode: undefined });
|
|
});
|
|
|
|
it("enforces allowList in custom mode string", () => {
|
|
mockNormalizedDirectMessage(SECONDARY_TARGET, PRIMARY_TARGET);
|
|
expectDeniedForTarget({ allowFrom: [SECONDARY_TARGET], mode: "broadcast" });
|
|
});
|
|
|
|
it("allows message in custom mode string when target is in allowList", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET, PRIMARY_TARGET);
|
|
expectAllowedForTarget({ allowFrom: [PRIMARY_TARGET], mode: "broadcast" });
|
|
});
|
|
});
|
|
|
|
describe("whitespace handling", () => {
|
|
it("trims whitespace from to parameter", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET);
|
|
|
|
expectResolutionOk(
|
|
{
|
|
to: ` ${PRIMARY_TARGET} `,
|
|
allowFrom: undefined,
|
|
mode: undefined,
|
|
},
|
|
PRIMARY_TARGET,
|
|
);
|
|
expect(vi.mocked(normalize.normalizeWhatsAppTarget)).toHaveBeenCalledWith(PRIMARY_TARGET);
|
|
});
|
|
|
|
it("trims whitespace from allowList entries", () => {
|
|
mockNormalizedDirectMessage(PRIMARY_TARGET, PRIMARY_TARGET);
|
|
|
|
resolveWhatsAppOutboundTarget({
|
|
to: PRIMARY_TARGET,
|
|
allowFrom: [` ${PRIMARY_TARGET} `],
|
|
mode: undefined,
|
|
});
|
|
|
|
expect(vi.mocked(normalize.normalizeWhatsAppTarget)).toHaveBeenCalledWith(PRIMARY_TARGET);
|
|
});
|
|
});
|
|
});
|