Files
openclaw/src/plugin-sdk/direct-dm.test.ts
2026-03-22 09:57:51 -07:00

170 lines
5.6 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import {
createDirectDmPreCryptoGuardPolicy,
createPreCryptoDirectDmAuthorizer,
dispatchInboundDirectDmWithRuntime,
resolveInboundDirectDmAccessWithRuntime,
} from "./direct-dm.js";
const baseCfg = {
commands: { useAccessGroups: true },
} as unknown as OpenClawConfig;
describe("plugin-sdk/direct-dm", () => {
it("resolves inbound DM access and command auth through one helper", async () => {
const result = await resolveInboundDirectDmAccessWithRuntime({
cfg: baseCfg,
channel: "nostr",
accountId: "default",
dmPolicy: "pairing",
allowFrom: [],
senderId: "paired-user",
rawBody: "/status",
isSenderAllowed: (senderId, allowFrom) => allowFrom.includes(senderId),
readStoreAllowFrom: async () => ["paired-user"],
runtime: {
shouldComputeCommandAuthorized: () => true,
resolveCommandAuthorizedFromAuthorizers: ({ authorizers }) =>
authorizers.some((entry) => entry.configured && entry.allowed),
},
modeWhenAccessGroupsOff: "configured",
});
expect(result.access.decision).toBe("allow");
expect(result.access.effectiveAllowFrom).toEqual(["paired-user"]);
expect(result.senderAllowedForCommands).toBe(true);
expect(result.commandAuthorized).toBe(true);
});
it("creates a pre-crypto authorizer that issues pairing and blocks unknown senders", async () => {
const issuePairingChallenge = vi.fn(async () => {});
const onBlocked = vi.fn();
const authorizer = createPreCryptoDirectDmAuthorizer({
resolveAccess: async (senderId) => ({
access:
senderId === "pair-me"
? {
decision: "pairing" as const,
reasonCode: "dm_policy_pairing_required",
reason: "dmPolicy=pairing (not allowlisted)",
effectiveAllowFrom: [],
}
: {
decision: "block" as const,
reasonCode: "dm_policy_disabled",
reason: "dmPolicy=disabled",
effectiveAllowFrom: [],
},
}),
issuePairingChallenge,
onBlocked,
});
await expect(
authorizer({
senderId: "pair-me",
reply: async () => {},
}),
).resolves.toBe("pairing");
await expect(
authorizer({
senderId: "blocked",
reply: async () => {},
}),
).resolves.toBe("block");
expect(issuePairingChallenge).toHaveBeenCalledTimes(1);
expect(onBlocked).toHaveBeenCalledWith({
senderId: "blocked",
reason: "dmPolicy=disabled",
reasonCode: "dm_policy_disabled",
});
});
it("builds a shared pre-crypto guard policy with partial overrides", () => {
const policy = createDirectDmPreCryptoGuardPolicy({
maxFutureSkewSec: 30,
rateLimit: {
maxPerSenderPerWindow: 5,
},
});
expect(policy.allowedKinds).toEqual([4]);
expect(policy.maxFutureSkewSec).toBe(30);
expect(policy.maxCiphertextBytes).toBe(16 * 1024);
expect(policy.rateLimit.maxPerSenderPerWindow).toBe(5);
expect(policy.rateLimit.maxGlobalPerWindow).toBe(200);
});
it("dispatches direct DMs through the standard route/session/reply pipeline", async () => {
const recordInboundSession = vi.fn(async () => {});
const dispatchReplyWithBufferedBlockDispatcher = vi.fn(async ({ dispatcherOptions }) => {
await dispatcherOptions.deliver({ text: "reply text" });
});
const deliver = vi.fn(async () => {});
const result = await dispatchInboundDirectDmWithRuntime({
cfg: {
session: { store: { type: "jsonl" } },
} as never,
runtime: {
channel: {
routing: {
resolveAgentRoute: vi.fn(({ accountId, peer }) => ({
agentId: "agent-main",
accountId,
sessionKey: `dm:${peer.id}`,
})),
},
session: {
resolveStorePath: vi.fn(() => "/tmp/direct-dm-session-store"),
readSessionUpdatedAt: vi.fn(() => 1234),
recordInboundSession,
},
reply: {
resolveEnvelopeFormatOptions: vi.fn(() => ({ mode: "agent" })),
formatAgentEnvelope: vi.fn(({ body }) => `env:${body}`),
finalizeInboundContext: vi.fn((ctx) => ctx),
dispatchReplyWithBufferedBlockDispatcher,
},
},
} as never,
channel: "nostr",
channelLabel: "Nostr",
accountId: "default",
peer: { kind: "direct", id: "sender-1" },
senderId: "sender-1",
senderAddress: "nostr:sender-1",
recipientAddress: "nostr:bot-1",
conversationLabel: "sender-1",
rawBody: "hello world",
messageId: "event-123",
timestamp: 1_710_000_000_000,
commandAuthorized: true,
deliver,
onRecordError: () => {},
onDispatchError: () => {},
});
expect(result.route).toMatchObject({
agentId: "agent-main",
accountId: "default",
sessionKey: "dm:sender-1",
});
expect(result.storePath).toBe("/tmp/direct-dm-session-store");
expect(result.ctxPayload).toMatchObject({
Body: "env:hello world",
BodyForAgent: "hello world",
From: "nostr:sender-1",
To: "nostr:bot-1",
SenderId: "sender-1",
MessageSid: "event-123",
CommandAuthorized: true,
});
expect(recordInboundSession).toHaveBeenCalledTimes(1);
expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1);
expect(deliver).toHaveBeenCalledWith({ text: "reply text" });
});
});