test(extensions): move legacy schema assertions

This commit is contained in:
Peter Steinberger
2026-04-20 21:57:34 +01:00
parent 29a5ab9632
commit 8134fe737c
11 changed files with 237 additions and 270 deletions

View File

@@ -64,6 +64,18 @@ describe("discord config schema", () => {
expect(cfg.maxLinesPerMessage).toBe(17);
});
it("defaults groupPolicy to allowlist", () => {
const cfg = expectValidDiscordConfig({});
expect(cfg.groupPolicy).toBe("allowlist");
});
it("accepts historyLimit", () => {
const cfg = expectValidDiscordConfig({ historyLimit: 3 });
expect(cfg.historyLimit).toBe(3);
});
it("loads guild map and dm group settings", () => {
const cfg = expectValidDiscordConfig({
enabled: true,

View File

@@ -2,6 +2,63 @@ import { describe, expect, it } from "vitest";
import { IMessageConfigSchema } from "../config-api.js";
describe("imessage config schema", () => {
it('accepts dmPolicy="open" with allowFrom "*"', () => {
const res = IMessageConfigSchema.safeParse({ dmPolicy: "open", allowFrom: ["*"] });
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.dmPolicy).toBe("open");
}
});
it('rejects dmPolicy="open" without allowFrom "*"', () => {
const res = IMessageConfigSchema.safeParse({
dmPolicy: "open",
allowFrom: ["+15555550123"],
});
expect(res.success).toBe(false);
if (!res.success) {
expect(res.error.issues[0]?.path.join(".")).toBe("allowFrom");
}
});
it("defaults dm/group policy", () => {
const res = IMessageConfigSchema.safeParse({});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.dmPolicy).toBe("pairing");
expect(res.data.groupPolicy).toBe("allowlist");
}
});
it("accepts historyLimit", () => {
const res = IMessageConfigSchema.safeParse({ historyLimit: 5 });
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.historyLimit).toBe(5);
}
});
it("rejects unsafe executable config values", () => {
const res = IMessageConfigSchema.safeParse({ cliPath: "imsg; rm -rf /" });
expect(res.success).toBe(false);
if (!res.success) {
expect(res.error.issues[0]?.path.join(".")).toBe("cliPath");
}
});
it("accepts path-like executable values with spaces", () => {
const res = IMessageConfigSchema.safeParse({
cliPath: "/Applications/Imsg Tools/imsg",
});
expect(res.success).toBe(true);
});
it("accepts textChunkLimit", () => {
const res = IMessageConfigSchema.safeParse({
enabled: true,

View File

@@ -2,6 +2,24 @@ import { describe, expect, it } from "vitest";
import { MSTeamsConfigSchema } from "../config-api.js";
describe("msteams config schema", () => {
it("defaults groupPolicy to allowlist", () => {
const res = MSTeamsConfigSchema.safeParse({});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.groupPolicy).toBe("allowlist");
}
});
it("accepts historyLimit", () => {
const res = MSTeamsConfigSchema.safeParse({ historyLimit: 4 });
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.historyLimit).toBe(4);
}
});
it("accepts replyStyle at global/team/channel levels", () => {
const res = MSTeamsConfigSchema.safeParse({
replyStyle: "top-level",

View File

@@ -16,6 +16,43 @@ function expectInvalidSignalConfig(config: unknown) {
}
describe("signal groups schema", () => {
it('rejects dmPolicy="open" without allowFrom "*"', () => {
const issues = expectInvalidSignalConfig({
dmPolicy: "open",
allowFrom: ["+15555550123"],
});
expect(issues[0]?.path.join(".")).toBe("allowFrom");
});
it('accepts dmPolicy="open" with allowFrom "*"', () => {
const res = SignalConfigSchema.safeParse({ dmPolicy: "open", allowFrom: ["*"] });
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.dmPolicy).toBe("open");
}
});
it("defaults dm/group policy", () => {
const res = SignalConfigSchema.safeParse({});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.dmPolicy).toBe("pairing");
expect(res.data.groupPolicy).toBe("allowlist");
}
});
it("accepts historyLimit", () => {
const res = SignalConfigSchema.safeParse({ historyLimit: 6 });
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.historyLimit).toBe(6);
}
});
it("accepts textChunkLimit", () => {
const res = SignalConfigSchema.safeParse({
enabled: true,

View File

@@ -15,6 +15,28 @@ function expectSlackConfigIssue(config: unknown, path: string) {
}
describe("slack config schema", () => {
it("defaults groupPolicy to allowlist", () => {
const res = SlackConfigSchema.safeParse({});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.groupPolicy).toBe("allowlist");
}
});
it("accepts historyLimit overrides per account", () => {
const res = SlackConfigSchema.safeParse({
historyLimit: 7,
accounts: { ops: { historyLimit: 2 } },
});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.historyLimit).toBe(7);
expect(res.data.accounts?.ops?.historyLimit).toBe(2);
}
});
it('rejects dmPolicy="open" without allowFrom "*"', () => {
expectSlackConfigIssue(
{

View File

@@ -14,6 +14,45 @@ function expectTelegramConfigIssue(config: unknown, path: string) {
}
describe("telegram custom commands schema", () => {
it('rejects dmPolicy="open" without allowFrom "*"', () => {
expectTelegramConfigIssue(
{ dmPolicy: "open", allowFrom: ["123456789"], botToken: "fake" },
"allowFrom",
);
});
it('accepts dmPolicy="open" with allowFrom "*"', () => {
const res = TelegramConfigSchema.safeParse({ dmPolicy: "open", allowFrom: ["*"] });
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.dmPolicy).toBe("open");
}
});
it("defaults dm/group policy", () => {
const res = TelegramConfigSchema.safeParse({});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.dmPolicy).toBe("pairing");
expect(res.data.groupPolicy).toBe("allowlist");
}
});
it("accepts historyLimit overrides per account", () => {
const res = TelegramConfigSchema.safeParse({
historyLimit: 8,
accounts: { ops: { historyLimit: 3 } },
});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.historyLimit).toBe(8);
expect(res.data.accounts?.ops?.historyLimit).toBe(3);
}
});
it("accepts textChunkLimit", () => {
const res = TelegramConfigSchema.safeParse({
enabled: true,

View File

@@ -8,6 +8,50 @@ function expectWhatsAppConfigValid(config: unknown) {
}
describe("whatsapp config schema", () => {
it('rejects dmPolicy="open" without allowFrom "*"', () => {
const res = WhatsAppConfigSchema.safeParse({
dmPolicy: "open",
allowFrom: ["+15555550123"],
});
expect(res.success).toBe(false);
if (!res.success) {
expect(res.error.issues[0]?.path.join(".")).toBe("allowFrom");
}
});
it('accepts dmPolicy="open" with allowFrom "*"', () => {
const res = WhatsAppConfigSchema.safeParse({ dmPolicy: "open", allowFrom: ["*"] });
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.dmPolicy).toBe("open");
}
});
it("defaults dm/group policy", () => {
const res = WhatsAppConfigSchema.safeParse({});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.dmPolicy).toBe("pairing");
expect(res.data.groupPolicy).toBe("allowlist");
}
});
it("accepts historyLimit overrides per account", () => {
const res = WhatsAppConfigSchema.safeParse({
historyLimit: 9,
accounts: { work: { historyLimit: 4 } },
});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.historyLimit).toBe(9);
expect(res.data.accounts?.work?.historyLimit).toBe(4);
}
});
it("accepts textChunkLimit", () => {
const res = expectWhatsAppConfigValid({
allowFrom: ["+15555550123"],

View File

@@ -2,16 +2,9 @@ import { describe, expect, it } from "vitest";
import {
expectSchemaConfigValue,
expectSchemaValid,
expectSchemaValidationIssue,
} from "./legacy-config-detection.test-support.js";
import { AudioSchema, BindingsSchema } from "./zod-schema.agents.js";
import { OpenClawSchema } from "./zod-schema.js";
import {
DiscordConfigSchema,
IMessageConfigSchema,
MSTeamsConfigSchema,
SlackConfigSchema,
} from "./zod-schema.providers-core.js";
function expectOpenClawSchemaInvalidPreservesField(params: {
config: unknown;
@@ -36,121 +29,11 @@ function expectOpenClawSchemaInvalidPreservesField(params: {
}
describe("legacy config detection", () => {
it('accepts imessage.dmPolicy="open" with allowFrom "*"', () => {
expectSchemaConfigValue({
schema: IMessageConfigSchema,
config: { dmPolicy: "open", allowFrom: ["*"] },
readValue: (config) => (config as { dmPolicy?: string }).dmPolicy,
expectedValue: "open",
});
});
it("defaults imessage.dmPolicy to pairing when imessage section exists", () => {
expectSchemaConfigValue({
schema: IMessageConfigSchema,
config: {},
readValue: (config) => (config as { dmPolicy?: string }).dmPolicy,
expectedValue: "pairing",
});
});
it("defaults imessage.groupPolicy to allowlist when imessage section exists", () => {
expectSchemaConfigValue({
schema: IMessageConfigSchema,
config: {},
readValue: (config) => (config as { groupPolicy?: string }).groupPolicy,
expectedValue: "allowlist",
});
});
it.each([
[
"defaults discord.groupPolicy to allowlist when discord section exists",
DiscordConfigSchema,
{},
(config: unknown) => (config as { groupPolicy?: string }).groupPolicy,
"allowlist",
],
[
"defaults slack.groupPolicy to allowlist when slack section exists",
SlackConfigSchema,
{},
(config: unknown) => (config as { groupPolicy?: string }).groupPolicy,
"allowlist",
],
[
"defaults msteams.groupPolicy to allowlist when msteams section exists",
MSTeamsConfigSchema,
{},
(config: unknown) => (config as { groupPolicy?: string }).groupPolicy,
"allowlist",
],
])("defaults: %s", (_name, schema, config, readValue, expectedValue) => {
expectSchemaConfigValue({ schema, config, readValue, expectedValue });
});
it("rejects unsafe executable config values", () => {
expectSchemaValidationIssue({
schema: IMessageConfigSchema,
config: { cliPath: "imsg; rm -rf /" },
expectedPath: "cliPath",
});
});
it("accepts tools audio transcription without cli", () => {
expectSchemaValid(AudioSchema, {
transcription: { command: ["whisper", "--model", "base"] },
});
});
it("accepts path-like executable values with spaces", () => {
expectSchemaValid(IMessageConfigSchema, {
cliPath: "/Applications/Imsg Tools/imsg",
});
});
it.each([
[
'rejects discord.dm.policy="open" without allowFrom "*"',
DiscordConfigSchema,
{ dm: { policy: "open", allowFrom: ["123"] } },
"dm.allowFrom",
],
[
'rejects discord.dmPolicy="open" without allowFrom "*"',
DiscordConfigSchema,
{ dmPolicy: "open", allowFrom: ["123"] },
"allowFrom",
],
[
'rejects slack.dm.policy="open" without allowFrom "*"',
SlackConfigSchema,
{ dm: { policy: "open", allowFrom: ["U123"] } },
"dm.allowFrom",
],
[
'rejects slack.dmPolicy="open" without allowFrom "*"',
SlackConfigSchema,
{ dmPolicy: "open", allowFrom: ["U123"] },
"allowFrom",
],
])("rejects: %s", (_name, schema, config, expectedPath) => {
expectSchemaValidationIssue({ schema, config, expectedPath });
});
it.each([
{
name: 'accepts discord dm.allowFrom="*" with top-level allowFrom alias',
schema: DiscordConfigSchema,
config: {
dm: { policy: "open", allowFrom: ["123"] },
allowFrom: ["*"],
},
},
{
name: 'accepts slack dm.allowFrom="*" with top-level allowFrom alias',
schema: SlackConfigSchema,
config: {
dm: { policy: "open", allowFrom: ["U123"] },
allowFrom: ["*"],
},
},
])("$name", ({ schema, config }) => {
expectSchemaValid(schema, config);
});
it("rejects legacy agent.model string", () => {
const res = OpenClawSchema.safeParse({
agent: { model: "anthropic/claude-opus-4-6" },

View File

@@ -1,18 +1,5 @@
import { describe, expect, it } from "vitest";
import {
expectSchemaConfigValue,
expectSchemaValidationIssue,
} from "./legacy-config-detection.test-support.js";
import { validateConfigObject } from "./validation.js";
import {
DiscordConfigSchema,
IMessageConfigSchema,
MSTeamsConfigSchema,
SignalConfigSchema,
SlackConfigSchema,
TelegramConfigSchema,
} from "./zod-schema.providers-core.js";
import { WhatsAppConfigSchema } from "./zod-schema.providers-whatsapp.js";
describe("legacy config detection", () => {
it.each([
@@ -102,139 +89,4 @@ describe("legacy config detection", () => {
}
},
);
it.each([
{
name: "telegram",
schema: TelegramConfigSchema,
allowFrom: ["123456789"],
expectedMessage: 'channels.telegram.dmPolicy="open"',
},
{
name: "whatsapp",
schema: WhatsAppConfigSchema,
allowFrom: ["+15555550123"],
expectedMessage: 'channels.whatsapp.dmPolicy="open"',
},
{
name: "signal",
schema: SignalConfigSchema,
allowFrom: ["+15555550123"],
expectedMessage: 'channels.signal.dmPolicy="open"',
},
{
name: "imessage",
schema: IMessageConfigSchema,
allowFrom: ["+15555550123"],
expectedMessage: 'channels.imessage.dmPolicy="open"',
},
] as const)(
'enforces dmPolicy="open" allowFrom wildcard for $name',
({ schema, allowFrom, expectedMessage }) => {
expectSchemaValidationIssue({
schema,
config: { dmPolicy: "open", allowFrom },
expectedPath: "allowFrom",
expectedMessage,
});
},
);
it.each([
{ name: "telegram", schema: TelegramConfigSchema },
{ name: "whatsapp", schema: WhatsAppConfigSchema },
{ name: "signal", schema: SignalConfigSchema },
] as const)('accepts dmPolicy="open" with wildcard for $name', ({ schema }) => {
expectSchemaConfigValue({
schema,
config: { dmPolicy: "open", allowFrom: ["*"] },
readValue: (config) => (config as { dmPolicy?: string }).dmPolicy,
expectedValue: "open",
});
});
it.each([
{ name: "telegram", schema: TelegramConfigSchema },
{ name: "whatsapp", schema: WhatsAppConfigSchema },
{ name: "signal", schema: SignalConfigSchema },
] as const)("defaults dm/group policy for configured provider $name", ({ schema }) => {
expectSchemaConfigValue({
schema,
config: {},
readValue: (config) => (config as { dmPolicy?: string }).dmPolicy,
expectedValue: "pairing",
});
expectSchemaConfigValue({
schema,
config: {},
readValue: (config) => (config as { groupPolicy?: string }).groupPolicy,
expectedValue: "allowlist",
});
});
it("accepts historyLimit overrides per provider and account", async () => {
expectSchemaConfigValue({
schema: WhatsAppConfigSchema,
config: { historyLimit: 9, accounts: { work: { historyLimit: 4 } } },
readValue: (config) => (config as { historyLimit?: number }).historyLimit,
expectedValue: 9,
});
expectSchemaConfigValue({
schema: WhatsAppConfigSchema,
config: { historyLimit: 9, accounts: { work: { historyLimit: 4 } } },
readValue: (config) =>
(config as { accounts?: { work?: { historyLimit?: number } } }).accounts?.work
?.historyLimit,
expectedValue: 4,
});
expectSchemaConfigValue({
schema: TelegramConfigSchema,
config: { historyLimit: 8, accounts: { ops: { historyLimit: 3 } } },
readValue: (config) => (config as { historyLimit?: number }).historyLimit,
expectedValue: 8,
});
expectSchemaConfigValue({
schema: TelegramConfigSchema,
config: { historyLimit: 8, accounts: { ops: { historyLimit: 3 } } },
readValue: (config) =>
(config as { accounts?: { ops?: { historyLimit?: number } } }).accounts?.ops?.historyLimit,
expectedValue: 3,
});
expectSchemaConfigValue({
schema: SlackConfigSchema,
config: { historyLimit: 7, accounts: { ops: { historyLimit: 2 } } },
readValue: (config) => (config as { historyLimit?: number }).historyLimit,
expectedValue: 7,
});
expectSchemaConfigValue({
schema: SlackConfigSchema,
config: { historyLimit: 7, accounts: { ops: { historyLimit: 2 } } },
readValue: (config) =>
(config as { accounts?: { ops?: { historyLimit?: number } } }).accounts?.ops?.historyLimit,
expectedValue: 2,
});
expectSchemaConfigValue({
schema: SignalConfigSchema,
config: { historyLimit: 6 },
readValue: (config) => (config as { historyLimit?: number }).historyLimit,
expectedValue: 6,
});
expectSchemaConfigValue({
schema: IMessageConfigSchema,
config: { historyLimit: 5 },
readValue: (config) => (config as { historyLimit?: number }).historyLimit,
expectedValue: 5,
});
expectSchemaConfigValue({
schema: MSTeamsConfigSchema,
config: { historyLimit: 4 },
readValue: (config) => (config as { historyLimit?: number }).historyLimit,
expectedValue: 4,
});
expectSchemaConfigValue({
schema: DiscordConfigSchema,
config: { historyLimit: 3 },
readValue: (config) => (config as { historyLimit?: number }).historyLimit,
expectedValue: 3,
});
});
});

View File

@@ -9,7 +9,7 @@ import {
makeRegistry,
resetPluginAutoEnableTestState,
} from "./plugin-auto-enable.test-helpers.js";
import { WhatsAppConfigSchema } from "./zod-schema.providers-whatsapp.js";
import { validateConfigObject } from "./validation.js";
afterEach(() => {
resetPluginAutoEnableTestState();
@@ -330,7 +330,7 @@ describe("applyPluginAutoEnable core", () => {
});
expect(result.config.channels?.whatsapp?.enabled).toBe(true);
expect(WhatsAppConfigSchema.safeParse(result.config.channels?.whatsapp).success).toBe(true);
expect(validateConfigObject(result.config).ok).toBe(true);
});
it("appends built-in WhatsApp to restrictive plugins.allow during auto-enable", () => {
@@ -350,7 +350,7 @@ describe("applyPluginAutoEnable core", () => {
expect(result.config.channels?.whatsapp?.enabled).toBe(true);
expect(result.config.plugins?.allow).toEqual(["telegram", "whatsapp"]);
expect(WhatsAppConfigSchema.safeParse(result.config.channels?.whatsapp).success).toBe(true);
expect(validateConfigObject(result.config).ok).toBe(true);
});
it("preserves configured plugin entries in restrictive plugins.allow", () => {

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { __testing, validateConfigObjectRaw } from "./validation.js";
import { SignalConfigSchema } from "./zod-schema.providers-core.js";
function mapFirstIssue(
schema: { safeParse: (value: unknown) => { success: true } | { success: false; error: unknown } },
@@ -33,7 +33,10 @@ describe("config validation allowed-values metadata", () => {
});
it("keeps native enum messages while attaching allowed values metadata", () => {
const issue = mapFirstIssue(SignalConfigSchema, { dmPolicy: "maybe" });
const issue = mapFirstIssue(
z.object({ dmPolicy: z.enum(["pairing", "allowlist", "open", "disabled"]) }),
{ dmPolicy: "maybe" },
);
expect(issue.path).toBe("dmPolicy");
expect(issue.message).toContain("expected one of");
expect(issue.message).not.toContain("(allowed:");