test(extensions): move channel contracts out of core

This commit is contained in:
Peter Steinberger
2026-04-20 17:59:33 +01:00
parent 1f139c198a
commit ca2d89bc4d
34 changed files with 625 additions and 726 deletions

View File

@@ -0,0 +1,106 @@
import type { BaseProbeResult, ChannelDirectoryEntry } from "openclaw/plugin-sdk/channel-contract";
import type { OpenClawConfig } from "openclaw/plugin-sdk/testing";
import { describe, expect, expectTypeOf, it } from "vitest";
import {
listSlackDirectoryGroupsFromConfig,
listSlackDirectoryPeersFromConfig,
} from "../directory-contract-api.js";
import type { SlackProbe } from "./probe.js";
type DirectoryListFn = (params: {
cfg: OpenClawConfig;
accountId?: string;
query?: string | null;
limit?: number | null;
}) => Promise<ChannelDirectoryEntry[]>;
async function listDirectoryEntriesWithDefaults(listFn: DirectoryListFn, cfg: OpenClawConfig) {
return await listFn({
cfg,
accountId: "default",
query: null,
limit: null,
});
}
async function expectDirectoryIds(
listFn: DirectoryListFn,
cfg: OpenClawConfig,
expected: string[],
options?: { sorted?: boolean },
) {
const entries = await listDirectoryEntriesWithDefaults(listFn, cfg);
const ids = entries.map((entry) => entry.id);
expect(options?.sorted ? ids.toSorted((a, b) => a.localeCompare(b)) : ids).toEqual(expected);
}
describe("Slack directory contract", () => {
it("keeps public probe aligned with base contract", () => {
expectTypeOf<SlackProbe>().toMatchTypeOf<BaseProbeResult>();
});
it("lists peers/groups from config", async () => {
const cfg = {
channels: {
slack: {
botToken: "xoxb-test",
appToken: "xapp-test",
dm: { allowFrom: ["U123", "user:U999"] },
dms: { U234: {} },
channels: { C111: { users: ["U777"] } },
},
},
} as unknown as OpenClawConfig;
await expectDirectoryIds(
listSlackDirectoryPeersFromConfig,
cfg,
["user:u123", "user:u234", "user:u777", "user:u999"],
{ sorted: true },
);
await expectDirectoryIds(listSlackDirectoryGroupsFromConfig, cfg, ["channel:c111"]);
});
it("keeps directories readable when tokens are unresolved SecretRefs", async () => {
const envSecret = {
source: "env",
provider: "default",
id: "MISSING_TEST_SECRET",
} as const;
const cfg = {
channels: {
slack: {
botToken: envSecret,
appToken: envSecret,
dm: { allowFrom: ["U123"] },
channels: { C111: {} },
},
},
} as unknown as OpenClawConfig;
await expectDirectoryIds(listSlackDirectoryPeersFromConfig, cfg, ["user:u123"]);
await expectDirectoryIds(listSlackDirectoryGroupsFromConfig, cfg, ["channel:c111"]);
});
it("applies query and limit filtering for config-backed directories", async () => {
const cfg = {
channels: {
slack: {
botToken: "xoxb-test",
appToken: "xapp-test",
dm: { allowFrom: ["U100", "U200"] },
dms: { U300: {} },
},
},
} as unknown as OpenClawConfig;
const peers = await listSlackDirectoryPeersFromConfig({
cfg,
accountId: "default",
query: "user:u",
limit: 2,
});
expect(peers).toHaveLength(2);
expect(peers.every((entry) => entry.id.startsWith("user:u"))).toBe(true);
});
});

View File

@@ -0,0 +1,64 @@
import {
createTempHomeEnv,
expectChannelInboundContextContract,
type OpenClawConfig,
} from "openclaw/plugin-sdk/testing";
import { describe, expect, it } from "vitest";
import {
createInboundSlackTestContext,
prepareSlackMessage,
} from "../inbound-contract-test-api.js";
import type { ResolvedSlackAccount } from "./accounts.js";
import type { SlackMessageEvent } from "./types.js";
function createSlackAccount(config: ResolvedSlackAccount["config"] = {}): ResolvedSlackAccount {
return {
accountId: "default",
enabled: true,
botTokenSource: "config",
appTokenSource: "config",
userTokenSource: "none",
config,
replyToMode: config.replyToMode,
replyToModeByChatType: config.replyToModeByChatType,
dm: config.dm,
} as ResolvedSlackAccount;
}
function createSlackMessage(overrides: Partial<SlackMessageEvent>): SlackMessageEvent {
return {
type: "message",
channel: "D123",
channel_type: "im",
user: "U1",
text: "hi",
ts: "1.000",
...overrides,
};
}
describe("Slack inbound context contract", () => {
it("keeps inbound context finalized", async () => {
const tempHome = await createTempHomeEnv("openclaw-slack-inbound-contract-");
try {
const ctx = createInboundSlackTestContext({
cfg: {
channels: { slack: { enabled: true } },
} as OpenClawConfig,
});
ctx.resolveUserName = async () => ({ name: "Alice" }) as never;
const prepared = await prepareSlackMessage({
ctx,
account: createSlackAccount(),
message: createSlackMessage({}),
opts: { source: "message" },
});
expect(prepared).toBeTruthy();
expectChannelInboundContextContract(prepared!.ctxPayload);
} finally {
await tempHome.restore();
}
});
});

View File

@@ -3,8 +3,8 @@ import type { App } from "@slack/bolt";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
import { expectChannelInboundContextContract as expectInboundContextContract } from "openclaw/plugin-sdk/testing";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { expectChannelInboundContextContract as expectInboundContextContract } from "../../../../../src/channels/plugins/contracts/test-helpers.js";
import type { ResolvedSlackAccount } from "../../accounts.js";
import type { SlackMessageEvent } from "../../types.js";
import type { SlackMonitorContext } from "../context.js";