mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:30:44 +00:00
test(extensions): move channel contracts to owners
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
DEFAULT_IMESSAGE_ATTACHMENT_ROOTS,
|
||||
resolveIMessageAttachmentRoots,
|
||||
resolveIMessageRemoteAttachmentRoots,
|
||||
} from "../contract-api.js";
|
||||
|
||||
describe("iMessage channel-inbound-roots contract", () => {
|
||||
function expectResolvedRootsCase(resolve: () => string[], expected: readonly string[]) {
|
||||
expect(resolve()).toEqual(expected);
|
||||
}
|
||||
|
||||
const accountOverrideCfg = {
|
||||
channels: {
|
||||
imessage: {
|
||||
attachmentRoots: ["/Users/*/Library/Messages/Attachments"],
|
||||
remoteAttachmentRoots: ["/Volumes/shared/imessage"],
|
||||
accounts: {
|
||||
work: {
|
||||
attachmentRoots: ["/Users/work/Library/Messages/Attachments"],
|
||||
remoteAttachmentRoots: ["/srv/work/attachments"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
it("resolves configured attachment roots with account overrides", () => {
|
||||
expectResolvedRootsCase(
|
||||
() => resolveIMessageAttachmentRoots({ cfg: accountOverrideCfg, accountId: "work" }),
|
||||
["/Users/work/Library/Messages/Attachments", "/Users/*/Library/Messages/Attachments"],
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves configured remote attachment roots with account overrides", () => {
|
||||
expectResolvedRootsCase(
|
||||
() => resolveIMessageRemoteAttachmentRoots({ cfg: accountOverrideCfg, accountId: "work" }),
|
||||
[
|
||||
"/srv/work/attachments",
|
||||
"/Volumes/shared/imessage",
|
||||
"/Users/work/Library/Messages/Attachments",
|
||||
"/Users/*/Library/Messages/Attachments",
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it("matches iMessage account ids case-insensitively for attachment roots", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
imessage: {
|
||||
accounts: {
|
||||
Work: {
|
||||
attachmentRoots: ["/Users/work/Library/Messages/Attachments"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expectResolvedRootsCase(
|
||||
() => resolveIMessageAttachmentRoots({ cfg, accountId: "work" }),
|
||||
["/Users/work/Library/Messages/Attachments", ...DEFAULT_IMESSAGE_ATTACHMENT_ROOTS],
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to default iMessage attachment roots", () => {
|
||||
expectResolvedRootsCase(
|
||||
() => resolveIMessageAttachmentRoots({ cfg: {} as OpenClawConfig }),
|
||||
[...DEFAULT_IMESSAGE_ATTACHMENT_ROOTS],
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to default iMessage remote attachment roots", () => {
|
||||
expectResolvedRootsCase(
|
||||
() => resolveIMessageRemoteAttachmentRoots({ cfg: {} as OpenClawConfig }),
|
||||
[...DEFAULT_IMESSAGE_ATTACHMENT_ROOTS],
|
||||
);
|
||||
});
|
||||
});
|
||||
77
extensions/signal/src/dm-policy.contract.test.ts
Normal file
77
extensions/signal/src/dm-policy.contract.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
DM_GROUP_ACCESS_REASON,
|
||||
resolveDmGroupAccessWithLists,
|
||||
} from "openclaw/plugin-sdk/channel-policy";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isSignalSenderAllowed, type SignalSender } from "../contract-api.js";
|
||||
|
||||
type ChannelSmokeCase = {
|
||||
name: string;
|
||||
storeAllowFrom: string[];
|
||||
isSenderAllowed: (allowFrom: string[]) => boolean;
|
||||
};
|
||||
|
||||
const signalSender: SignalSender = {
|
||||
kind: "phone",
|
||||
raw: "+15550001111",
|
||||
e164: "+15550001111",
|
||||
};
|
||||
const signalSenderE164 = "+15550001111";
|
||||
|
||||
function createChannelSmokeCases(): ChannelSmokeCase[] {
|
||||
return [
|
||||
{
|
||||
name: "bluebubbles",
|
||||
storeAllowFrom: ["attacker-user"],
|
||||
isSenderAllowed: (allowFrom) => allowFrom.includes("attacker-user"),
|
||||
},
|
||||
{
|
||||
name: "signal",
|
||||
storeAllowFrom: [signalSenderE164],
|
||||
isSenderAllowed: (allowFrom) => isSignalSenderAllowed(signalSender, allowFrom),
|
||||
},
|
||||
{
|
||||
name: "mattermost",
|
||||
storeAllowFrom: ["user:attacker-user"],
|
||||
isSenderAllowed: (allowFrom) => allowFrom.includes("user:attacker-user"),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function expandChannelIngressCases(cases: readonly ChannelSmokeCase[]) {
|
||||
return cases.flatMap((testCase) =>
|
||||
(["message", "reaction"] as const).map((ingress) => ({
|
||||
testCase,
|
||||
ingress,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
describe("Signal dm-policy shared contract", () => {
|
||||
function expectBlockedGroupAccess(params: {
|
||||
storeAllowFrom: string[];
|
||||
isSenderAllowed: (allowFrom: string[]) => boolean;
|
||||
}) {
|
||||
const access = resolveDmGroupAccessWithLists({
|
||||
isGroup: true,
|
||||
dmPolicy: "pairing",
|
||||
groupPolicy: "allowlist",
|
||||
allowFrom: ["owner-user"],
|
||||
groupAllowFrom: ["group-owner"],
|
||||
storeAllowFrom: params.storeAllowFrom,
|
||||
isSenderAllowed: params.isSenderAllowed,
|
||||
});
|
||||
expect(access.decision).toBe("block");
|
||||
expect(access.reasonCode).toBe(DM_GROUP_ACCESS_REASON.GROUP_POLICY_NOT_ALLOWLISTED);
|
||||
expect(access.reason).toBe("groupPolicy=allowlist (not allowlisted)");
|
||||
}
|
||||
|
||||
it("blocks group ingress when sender is only in pairing store", () => {
|
||||
for (const { testCase } of expandChannelIngressCases(createChannelSmokeCases())) {
|
||||
expectBlockedGroupAccess({
|
||||
storeAllowFrom: testCase.storeAllowFrom,
|
||||
isSenderAllowed: testCase.isSenderAllowed,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user