test(extensions): move channel contracts to owners

This commit is contained in:
Peter Steinberger
2026-04-20 20:29:56 +01:00
parent 0f1ce47033
commit 9c9ca5f431
5 changed files with 10 additions and 215 deletions

View File

@@ -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],
);
});
});

View 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,
});
}
});
});