test(extensions): move remaining channel schema tests

This commit is contained in:
Peter Steinberger
2026-04-20 21:54:09 +01:00
parent 30eb467ec8
commit 3a7a1f156d
14 changed files with 336 additions and 364 deletions

View File

@@ -20,6 +20,50 @@ function expectInvalidDiscordConfig(config: unknown) {
}
describe("discord config schema", () => {
it('rejects dmPolicy="open" without allowFrom "*"', () => {
const issues = expectInvalidDiscordConfig({
dmPolicy: "open",
allowFrom: ["123"],
});
expect(issues[0]?.path.join(".")).toBe("allowFrom");
});
it('rejects dmPolicy="open" with empty allowFrom', () => {
const issues = expectInvalidDiscordConfig({
dmPolicy: "open",
allowFrom: [],
});
expect(issues[0]?.path.join(".")).toBe("allowFrom");
});
it('rejects legacy dm.policy="open" with empty dm.allowFrom', () => {
const issues = expectInvalidDiscordConfig({
dm: { policy: "open", allowFrom: [] },
});
expect(issues[0]?.path.join(".")).toBe("dm.allowFrom");
});
it('accepts legacy dm.policy="open" with top-level allowFrom alias', () => {
expectValidDiscordConfig({
dm: { policy: "open", allowFrom: ["123"] },
allowFrom: ["*"],
});
});
it("accepts textChunkLimit without reviving legacy message limits", () => {
const cfg = expectValidDiscordConfig({
enabled: true,
textChunkLimit: 1999,
maxLinesPerMessage: 17,
});
expect(cfg.textChunkLimit).toBe(1999);
expect(cfg.maxLinesPerMessage).toBe(17);
});
it("loads guild map and dm group settings", () => {
const cfg = expectValidDiscordConfig({
enabled: true,

View File

@@ -0,0 +1,16 @@
import { GoogleChatConfigSchema } from "openclaw/plugin-sdk/googlechat";
import { describe, expect, it } from "vitest";
describe("googlechat config schema", () => {
it("accepts serviceAccount refs", () => {
const result = GoogleChatConfigSchema.safeParse({
serviceAccountRef: {
source: "file",
provider: "filemain",
id: "/channels/googlechat/serviceAccount",
},
});
expect(result.success).toBe(true);
});
});

View File

@@ -2,6 +2,18 @@ import { describe, expect, it } from "vitest";
import { IMessageConfigSchema } from "../config-api.js";
describe("imessage config schema", () => {
it("accepts textChunkLimit", () => {
const res = IMessageConfigSchema.safeParse({
enabled: true,
textChunkLimit: 1111,
});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.textChunkLimit).toBe(1111);
}
});
it("accepts safe remoteHost", () => {
const res = IMessageConfigSchema.safeParse({
remoteHost: "bot@gateway-host",

View File

@@ -1,7 +1,58 @@
import { describe, expect, it } from "vitest";
import { IrcConfigSchema } from "./config-schema.js";
function expectValidConfig(result: ReturnType<typeof IrcConfigSchema.safeParse>) {
expect(result.success).toBe(true);
if (!result.success) {
throw new Error("expected config to be valid");
}
return result.data;
}
function expectInvalidConfig(result: ReturnType<typeof IrcConfigSchema.safeParse>) {
expect(result.success).toBe(false);
if (result.success) {
throw new Error("expected config to be invalid");
}
return result.error.issues;
}
describe("irc config schema", () => {
it("accepts basic config", () => {
const config = expectValidConfig(
IrcConfigSchema.safeParse({
host: "irc.libera.chat",
nick: "openclaw-bot",
channels: ["#openclaw"],
}),
);
expect(config.host).toBe("irc.libera.chat");
expect(config.nick).toBe("openclaw-bot");
});
it('rejects dmPolicy="open" without allowFrom "*"', () => {
const issues = expectInvalidConfig(
IrcConfigSchema.safeParse({
dmPolicy: "open",
allowFrom: ["alice"],
}),
);
expect(issues[0]?.path.join(".")).toBe("allowFrom");
});
it('accepts dmPolicy="open" with allowFrom "*"', () => {
const config = expectValidConfig(
IrcConfigSchema.safeParse({
dmPolicy: "open",
allowFrom: ["*"],
}),
);
expect(config.dmPolicy).toBe("open");
});
it("accepts numeric allowFrom and groupAllowFrom entries", () => {
const parsed = IrcConfigSchema.parse({
dmPolicy: "allowlist",
@@ -24,4 +75,42 @@ describe("irc config schema", () => {
expect(parsed.groups?.["#ops"]?.allowFrom).toEqual([42, "alice"]);
});
it("rejects nickserv register without registerEmail", () => {
const issues = expectInvalidConfig(
IrcConfigSchema.safeParse({
nickserv: {
register: true,
password: "secret",
},
}),
);
expect(issues[0]?.path.join(".")).toBe("nickserv.registerEmail");
});
it("accepts nickserv register with password and registerEmail", () => {
const config = expectValidConfig(
IrcConfigSchema.safeParse({
nickserv: {
register: true,
password: "secret",
registerEmail: "bot@example.com",
},
}),
);
expect(config.nickserv?.register).toBe(true);
});
it("accepts nickserv register with registerEmail only", () => {
expectValidConfig(
IrcConfigSchema.safeParse({
nickserv: {
register: true,
registerEmail: "bot@example.com",
},
}),
);
});
});

View File

@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { MSTeamsConfigSchema } from "./zod-schema.providers-core.js";
import { MSTeamsConfigSchema } from "../config-api.js";
describe("config msteams", () => {
describe("msteams config schema", () => {
it("accepts replyStyle at global/team/channel levels", () => {
const res = MSTeamsConfigSchema.safeParse({
replyStyle: "top-level",
@@ -14,6 +14,7 @@ describe("config msteams", () => {
},
},
});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.replyStyle).toBe("top-level");
@@ -26,6 +27,7 @@ describe("config msteams", () => {
const res = MSTeamsConfigSchema.safeParse({
replyStyle: "nope",
});
expect(res.success).toBe(false);
});
});

View File

@@ -16,6 +16,18 @@ function expectInvalidSignalConfig(config: unknown) {
}
describe("signal groups schema", () => {
it("accepts textChunkLimit", () => {
const res = SignalConfigSchema.safeParse({
enabled: true,
textChunkLimit: 2222,
});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.textChunkLimit).toBe(2222);
}
});
it("accepts accountUuid for loop protection", () => {
expectValidSignalConfig({
accountUuid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",

View File

@@ -0,0 +1,136 @@
import { describe, expect, it } from "vitest";
import { SlackConfigSchema } from "../config-api.js";
function expectSlackConfigValid(config: unknown) {
const res = SlackConfigSchema.safeParse(config);
expect(res.success).toBe(true);
}
function expectSlackConfigIssue(config: unknown, path: string) {
const res = SlackConfigSchema.safeParse(config);
expect(res.success).toBe(false);
if (!res.success) {
expect(res.error.issues.some((issue) => issue.path.join(".").includes(path))).toBe(true);
}
}
describe("slack config schema", () => {
it('rejects dmPolicy="open" without allowFrom "*"', () => {
expectSlackConfigIssue(
{
dmPolicy: "open",
allowFrom: ["U123"],
},
"allowFrom",
);
});
it('accepts legacy dm.policy="open" with top-level allowFrom alias', () => {
expectSlackConfigValid({
dm: { policy: "open", allowFrom: ["U123"] },
allowFrom: ["*"],
});
});
it("accepts user token config fields", () => {
expectSlackConfigValid({
botToken: "xoxb-any",
appToken: "xapp-any",
userToken: "xoxp-any",
userTokenReadOnly: false,
});
});
it("accepts account-level user token config", () => {
expectSlackConfigValid({
accounts: {
work: {
botToken: "xoxb-any",
appToken: "xapp-any",
userToken: "xoxp-any",
userTokenReadOnly: true,
},
},
});
});
it("rejects invalid userTokenReadOnly types", () => {
expectSlackConfigIssue(
{
botToken: "xoxb-any",
appToken: "xapp-any",
userToken: "xoxp-any",
userTokenReadOnly: "no",
},
"userTokenReadOnly",
);
});
it("rejects invalid userToken types", () => {
expectSlackConfigIssue(
{
botToken: "xoxb-any",
appToken: "xapp-any",
userToken: 123,
},
"userToken",
);
});
it("accepts HTTP mode when signing secret is configured", () => {
expectSlackConfigValid({
mode: "http",
signingSecret: "secret",
});
});
it("accepts HTTP mode when signing secret is configured as SecretRef", () => {
expectSlackConfigValid({
mode: "http",
signingSecret: { source: "env", provider: "default", id: "SLACK_SIGNING_SECRET" },
});
});
it("rejects HTTP mode without signing secret", () => {
expectSlackConfigIssue({ mode: "http" }, "signingSecret");
});
it("accepts account HTTP mode when base signing secret is set", () => {
expectSlackConfigValid({
signingSecret: "secret",
accounts: {
ops: {
mode: "http",
},
},
});
});
it("accepts account HTTP mode when account signing secret is set as SecretRef", () => {
expectSlackConfigValid({
accounts: {
ops: {
mode: "http",
signingSecret: {
source: "env",
provider: "default",
id: "SLACK_OPS_SIGNING_SECRET",
},
},
},
});
});
it("rejects account HTTP mode without signing secret", () => {
expectSlackConfigIssue(
{
accounts: {
ops: {
mode: "http",
},
},
},
"accounts.ops.signingSecret",
);
});
});

View File

@@ -14,6 +14,18 @@ function expectTelegramConfigIssue(config: unknown, path: string) {
}
describe("telegram custom commands schema", () => {
it("accepts textChunkLimit", () => {
const res = TelegramConfigSchema.safeParse({
enabled: true,
textChunkLimit: 3333,
});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.textChunkLimit).toBe(3333);
}
});
it("normalizes custom commands", () => {
const res = TelegramConfigSchema.safeParse({
customCommands: [{ command: "/Backup", description: " Git backup " }],

View File

@@ -8,6 +8,17 @@ function expectWhatsAppConfigValid(config: unknown) {
}
describe("whatsapp config schema", () => {
it("accepts textChunkLimit", () => {
const res = expectWhatsAppConfigValid({
allowFrom: ["+15555550123"],
textChunkLimit: 4444,
});
if (res.success) {
expect(res.data.textChunkLimit).toBe(4444);
}
});
it("accepts enabled", () => {
expectWhatsAppConfigValid({
enabled: true,

View File

@@ -1,122 +0,0 @@
import { describe, expect, it } from "vitest";
import { SlackConfigSchema } from "./zod-schema.providers-core.js";
function expectSlackConfigValid(config: unknown) {
expect(SlackConfigSchema.safeParse(config).success).toBe(true);
}
function expectSlackConfigIssue(config: unknown, path: string) {
const res = SlackConfigSchema.safeParse(config);
expect(res.success).toBe(false);
if (!res.success) {
expect(res.error.issues.some((issue) => issue.path.join(".").includes(path))).toBe(true);
}
}
describe("channel token and HTTP validation", () => {
describe("Slack token fields", () => {
it("accepts user token config fields", () => {
expectSlackConfigValid({
botToken: "xoxb-any",
appToken: "xapp-any",
userToken: "xoxp-any",
userTokenReadOnly: false,
});
});
it("accepts account-level user token config", () => {
expectSlackConfigValid({
accounts: {
work: {
botToken: "xoxb-any",
appToken: "xapp-any",
userToken: "xoxp-any",
userTokenReadOnly: true,
},
},
});
});
it("rejects invalid userTokenReadOnly types", () => {
expectSlackConfigIssue(
{
botToken: "xoxb-any",
appToken: "xapp-any",
userToken: "xoxp-any",
userTokenReadOnly: "no",
},
"userTokenReadOnly",
);
});
it("rejects invalid userToken types", () => {
expectSlackConfigIssue(
{
botToken: "xoxb-any",
appToken: "xapp-any",
userToken: 123,
},
"userToken",
);
});
});
describe("Slack HTTP mode", () => {
it("accepts HTTP mode when signing secret is configured", () => {
expectSlackConfigValid({
mode: "http",
signingSecret: "secret",
});
});
it("accepts HTTP mode when signing secret is configured as SecretRef", () => {
expectSlackConfigValid({
mode: "http",
signingSecret: { source: "env", provider: "default", id: "SLACK_SIGNING_SECRET" },
});
});
it("rejects HTTP mode without signing secret", () => {
expectSlackConfigIssue({ mode: "http" }, "signingSecret");
});
it("accepts account HTTP mode when base signing secret is set", () => {
expectSlackConfigValid({
signingSecret: "secret",
accounts: {
ops: {
mode: "http",
},
},
});
});
it("accepts account HTTP mode when account signing secret is set as SecretRef", () => {
expectSlackConfigValid({
accounts: {
ops: {
mode: "http",
signingSecret: {
source: "env",
provider: "default",
id: "SLACK_OPS_SIGNING_SECRET",
},
},
},
});
});
it("rejects account HTTP mode without signing secret", () => {
expectSlackConfigIssue(
{
accounts: {
ops: {
mode: "http",
},
},
},
"accounts.ops.signingSecret",
);
});
});
});

View File

@@ -11,13 +11,6 @@ import { findLegacyConfigIssues } from "./legacy.js";
import { buildWebSearchProviderConfig, withTempHome, writeOpenClawConfig } from "./test-helpers.js";
import { validateConfigObject, validateConfigObjectRaw } from "./validation.js";
import { OpenClawSchema } from "./zod-schema.js";
import {
DiscordConfigSchema,
IMessageConfigSchema,
SignalConfigSchema,
TelegramConfigSchema,
} from "./zod-schema.providers-core.js";
import { WhatsAppConfigSchema } from "./zod-schema.providers-whatsapp.js";
describe("$schema key in config (#14998)", () => {
it("accepts config with $schema string", () => {
@@ -503,58 +496,6 @@ describe("cron webhook schema", () => {
});
expect(res.success).toBe(true);
});
it("accepts channel textChunkLimit config without reviving legacy message limits", () => {
const whatsapp = WhatsAppConfigSchema.safeParse({
allowFrom: ["+15555550123"],
textChunkLimit: 4444,
});
const telegram = TelegramConfigSchema.safeParse({
enabled: true,
textChunkLimit: 3333,
});
const discord = DiscordConfigSchema.safeParse({
enabled: true,
textChunkLimit: 1999,
maxLinesPerMessage: 17,
});
const signal = SignalConfigSchema.safeParse({
enabled: true,
textChunkLimit: 2222,
});
const imessage = IMessageConfigSchema.safeParse({
enabled: true,
textChunkLimit: 1111,
});
const messages = {
messagePrefix: "[openclaw]",
responsePrefix: "🦞",
};
expect(whatsapp.success).toBe(true);
expect(telegram.success).toBe(true);
expect(discord.success).toBe(true);
expect(signal.success).toBe(true);
expect(imessage.success).toBe(true);
if (whatsapp.success) {
expect(whatsapp.data.textChunkLimit).toBe(4444);
}
if (telegram.success) {
expect(telegram.data.textChunkLimit).toBe(3333);
}
if (discord.success) {
expect(discord.data.textChunkLimit).toBe(1999);
expect(discord.data.maxLinesPerMessage).toBe(17);
}
if (signal.success) {
expect(signal.data.textChunkLimit).toBe(2222);
}
if (imessage.success) {
expect(imessage.data.textChunkLimit).toBe(1111);
}
const legacy = messages as unknown as Record<string, unknown>;
expect(legacy.textChunkLimit).toBeUndefined();
});
});
describe("broadcast", () => {

View File

@@ -1,63 +0,0 @@
import { describe, expect, it } from "vitest";
import { DiscordConfigSchema, SlackConfigSchema } from "./zod-schema.providers-core.js";
describe("DM policy aliases (Slack/Discord)", () => {
it('rejects discord dmPolicy="open" without allowFrom "*"', () => {
const res = DiscordConfigSchema.safeParse({
dmPolicy: "open",
allowFrom: ["123"],
});
expect(res.success).toBe(false);
if (!res.success) {
expect(res.error.issues[0]?.path.join(".")).toBe("allowFrom");
}
});
it('rejects discord dmPolicy="open" with empty allowFrom', () => {
const res = DiscordConfigSchema.safeParse({
dmPolicy: "open",
allowFrom: [],
});
expect(res.success).toBe(false);
if (!res.success) {
expect(res.error.issues[0]?.path.join(".")).toBe("allowFrom");
}
});
it('rejects discord legacy dm.policy="open" with empty dm.allowFrom', () => {
const res = DiscordConfigSchema.safeParse({
dm: { policy: "open", allowFrom: [] },
});
expect(res.success).toBe(false);
if (!res.success) {
expect(res.error.issues[0]?.path.join(".")).toBe("dm.allowFrom");
}
});
it('accepts discord legacy dm.policy="open" with top-level allowFrom alias', () => {
const res = DiscordConfigSchema.safeParse({
dm: { policy: "open", allowFrom: ["123"] },
allowFrom: ["*"],
});
expect(res.success).toBe(true);
});
it('rejects slack dmPolicy="open" without allowFrom "*"', () => {
const res = SlackConfigSchema.safeParse({
dmPolicy: "open",
allowFrom: ["U123"],
});
expect(res.success).toBe(false);
if (!res.success) {
expect(res.error.issues[0]?.path.join(".")).toBe("allowFrom");
}
});
it('accepts slack legacy dm.policy="open" with top-level allowFrom alias', () => {
const res = SlackConfigSchema.safeParse({
dm: { policy: "open", allowFrom: ["U123"] },
allowFrom: ["*"],
});
expect(res.success).toBe(true);
});
});

View File

@@ -1,105 +0,0 @@
import { describe, expect, it } from "vitest";
import { IrcConfigSchema } from "./zod-schema.providers-core.js";
function expectValidConfig(result: ReturnType<typeof IrcConfigSchema.safeParse>) {
expect(result.success).toBe(true);
if (!result.success) {
throw new Error("expected config to be valid");
}
return result.data;
}
function expectInvalidConfig(result: ReturnType<typeof IrcConfigSchema.safeParse>) {
expect(result.success).toBe(false);
if (result.success) {
throw new Error("expected config to be invalid");
}
return result.error.issues;
}
describe("config irc", () => {
it("accepts basic irc config", () => {
const res = IrcConfigSchema.safeParse({
host: "irc.libera.chat",
nick: "openclaw-bot",
channels: ["#openclaw"],
});
const config = expectValidConfig(res);
expect(config.host).toBe("irc.libera.chat");
expect(config.nick).toBe("openclaw-bot");
});
it('rejects irc.dmPolicy="open" without allowFrom "*"', () => {
const res = IrcConfigSchema.safeParse({
dmPolicy: "open",
allowFrom: ["alice"],
});
const issues = expectInvalidConfig(res);
expect(issues[0]?.path.join(".")).toBe("allowFrom");
});
it('accepts irc.dmPolicy="open" with allowFrom "*"', () => {
const res = IrcConfigSchema.safeParse({
dmPolicy: "open",
allowFrom: ["*"],
});
const config = expectValidConfig(res);
expect(config.dmPolicy).toBe("open");
});
it("accepts mixed allowFrom value types for IRC", () => {
const res = IrcConfigSchema.safeParse({
allowFrom: [12345, "alice"],
groupAllowFrom: [67890, "alice!ident@example.org"],
groups: {
"#ops": {
allowFrom: [42, "alice"],
},
},
});
const config = expectValidConfig(res);
expect(config.allowFrom).toEqual([12345, "alice"]);
expect(config.groupAllowFrom).toEqual([67890, "alice!ident@example.org"]);
expect(config.groups?.["#ops"]?.allowFrom).toEqual([42, "alice"]);
});
it("rejects nickserv register without registerEmail", () => {
const res = IrcConfigSchema.safeParse({
nickserv: {
register: true,
password: "secret",
},
});
const issues = expectInvalidConfig(res);
expect(issues[0]?.path.join(".")).toBe("nickserv.registerEmail");
});
it("accepts nickserv register with password and registerEmail", () => {
const res = IrcConfigSchema.safeParse({
nickserv: {
register: true,
password: "secret",
registerEmail: "bot@example.com",
},
});
const config = expectValidConfig(res);
expect(config.nickserv?.register).toBe(true);
});
it("accepts nickserv register with registerEmail only (password may come from env)", () => {
const res = IrcConfigSchema.safeParse({
nickserv: {
register: true,
registerEmail: "bot@example.com",
},
});
expectValidConfig(res);
});
});

View File

@@ -4,7 +4,6 @@ import {
VALID_EXEC_SECRET_REF_IDS,
} from "../test-utils/secret-ref-test-vectors.js";
import { validateConfigObjectRaw } from "./validation.js";
import { GoogleChatConfigSchema } from "./zod-schema.providers-core.js";
function validateOpenAiApiKeyRef(apiKey: unknown) {
return validateConfigObjectRaw({
@@ -70,18 +69,6 @@ describe("config secret refs schema", () => {
expect(result.ok).toBe(true);
});
it("accepts googlechat serviceAccount refs", () => {
const result = GoogleChatConfigSchema.safeParse({
serviceAccountRef: {
source: "file",
provider: "filemain",
id: "/channels/googlechat/serviceAccount",
},
});
expect(result.success).toBe(true);
});
it("accepts skills entry apiKey refs", () => {
const result = validateConfigObjectRaw({
skills: {