test: fold identity defaults into existing config suites

This commit is contained in:
Peter Steinberger
2026-04-08 00:58:02 +01:00
parent b998156083
commit cddfd7781e
4 changed files with 189 additions and 191 deletions

View File

@@ -295,6 +295,99 @@ describe("gateway.channelHealthCheckMinutes", () => {
});
});
describe("config identity/materialization regressions", () => {
it("keeps explicit responsePrefix and group mention patterns", () => {
const res = validateConfigObject({
agents: {
list: [
{
id: "main",
identity: {
name: "Samantha Sloth",
theme: "space lobster",
emoji: "🦞",
},
groupChat: { mentionPatterns: ["@openclaw"] },
},
],
},
messages: {
responsePrefix: "✅",
},
});
expect(res.ok).toBe(true);
if (res.ok) {
expect(res.config.messages?.responsePrefix).toBe("✅");
expect(res.config.agents?.list?.[0]?.groupChat?.mentionPatterns).toEqual(["@openclaw"]);
}
});
it("preserves empty responsePrefix when identity is present", () => {
const res = validateConfigObject({
agents: {
list: [
{
id: "main",
identity: {
name: "Samantha",
theme: "helpful sloth",
emoji: "🦥",
},
},
],
},
messages: {
responsePrefix: "",
},
});
expect(res.ok).toBe(true);
if (res.ok) {
expect(res.config.messages?.responsePrefix).toBe("");
}
});
it("accepts blank model provider apiKey values", () => {
const res = validateConfigObject({
models: {
mode: "merge",
providers: {
minimax: {
baseUrl: "https://api.minimax.io/anthropic",
apiKey: "",
api: "anthropic-messages",
models: [
{
id: "MiniMax-M2.7",
name: "MiniMax M2.7",
reasoning: false,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 200000,
maxTokens: 8192,
},
],
},
},
},
});
expect(res.ok).toBe(true);
if (res.ok) {
expect(res.config.models?.providers?.minimax?.baseUrl).toBe(
"https://api.minimax.io/anthropic",
);
expect(res.config.models?.providers?.minimax?.apiKey).toBe("");
}
});
});
describe("cron webhook schema", () => {
it("accepts cron.webhookToken and legacy cron.webhook", () => {
const res = OpenClawSchema.safeParse({
@@ -345,6 +438,38 @@ describe("cron webhook schema", () => {
});
expect(res.success).toBe(true);
});
it("accepts channel textChunkLimit config without reviving legacy message limits", () => {
const res = OpenClawSchema.safeParse({
messages: {
messagePrefix: "[openclaw]",
responsePrefix: "🦞",
},
channels: {
whatsapp: { allowFrom: ["+15555550123"], textChunkLimit: 4444 },
telegram: { enabled: true, textChunkLimit: 3333 },
discord: {
enabled: true,
textChunkLimit: 1999,
maxLinesPerMessage: 17,
},
signal: { enabled: true, textChunkLimit: 2222 },
imessage: { enabled: true, textChunkLimit: 1111 },
},
});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.channels?.whatsapp?.textChunkLimit).toBe(4444);
expect(res.data.channels?.telegram?.textChunkLimit).toBe(3333);
expect(res.data.channels?.discord?.textChunkLimit).toBe(1999);
expect(res.data.channels?.discord?.maxLinesPerMessage).toBe(17);
expect(res.data.channels?.signal?.textChunkLimit).toBe(2222);
expect(res.data.channels?.imessage?.textChunkLimit).toBe(1111);
const legacy = (res.data.messages as unknown as Record<string, unknown>).textChunkLimit;
expect(legacy).toBeUndefined();
}
});
});
describe("broadcast", () => {

View File

@@ -1,190 +0,0 @@
import { describe, expect, it } from "vitest";
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
import { validateConfigObject, validateConfigObjectRaw } from "./validation.js";
import { OpenClawSchema } from "./zod-schema.js";
const defaultIdentity = {
name: "Samantha",
theme: "helpful sloth",
emoji: "🦥",
};
const configWithDefaultIdentity = (messages: Record<string, unknown>) => ({
agents: {
list: [
{
id: "main",
identity: defaultIdentity,
},
],
},
messages,
});
function expectValidConfig(raw: unknown) {
const result = validateConfigObject(raw);
expect(result.ok).toBe(true);
if (!result.ok) {
throw new Error(`expected config to validate: ${JSON.stringify(result.issues)}`);
}
return result.config;
}
function expectValidRawConfig(raw: unknown) {
const result = validateConfigObjectRaw(raw);
expect(result.ok).toBe(true);
if (!result.ok) {
throw new Error(`expected raw config to validate: ${JSON.stringify(result.issues)}`);
}
return result.config;
}
describe("config identity defaults", () => {
it("does not derive mention defaults and only sets ackReactionScope when identity is present", () => {
const cfg = expectValidConfig(configWithDefaultIdentity({}));
expect(cfg.messages?.responsePrefix).toBeUndefined();
expect(cfg.messages?.groupChat?.mentionPatterns).toBeUndefined();
expect(cfg.messages?.ackReaction).toBeUndefined();
expect(cfg.messages?.ackReactionScope).toBe("group-mentions");
});
it("keeps ackReaction unset and does not synthesize agent/session defaults when identity is missing", () => {
const cfg = expectValidConfig({ messages: {} });
expect(cfg.messages?.ackReaction).toBeUndefined();
expect(cfg.messages?.ackReactionScope).toBe("group-mentions");
expect(cfg.messages?.responsePrefix).toBeUndefined();
expect(cfg.messages?.groupChat?.mentionPatterns).toBeUndefined();
expect(cfg.agents?.list).toBeUndefined();
expect(cfg.agents?.defaults?.maxConcurrent).toBe(DEFAULT_AGENT_MAX_CONCURRENT);
expect(cfg.agents?.defaults?.subagents?.maxConcurrent).toBe(DEFAULT_SUBAGENT_MAX_CONCURRENT);
expect(cfg.session).toBeUndefined();
});
it("does not override explicit values", () => {
const cfg = expectValidConfig({
agents: {
list: [
{
id: "main",
identity: {
name: "Samantha Sloth",
theme: "space lobster",
emoji: "🦞",
},
groupChat: { mentionPatterns: ["@openclaw"] },
},
],
},
messages: {
responsePrefix: "✅",
},
});
expect(cfg.messages?.responsePrefix).toBe("✅");
expect(cfg.agents?.list?.[0]?.groupChat?.mentionPatterns).toEqual(["@openclaw"]);
});
it("supports provider textChunkLimit config", () => {
const result = OpenClawSchema.safeParse({
messages: {
messagePrefix: "[openclaw]",
responsePrefix: "🦞",
},
channels: {
whatsapp: { allowFrom: ["+15555550123"], textChunkLimit: 4444 },
telegram: { enabled: true, textChunkLimit: 3333 },
discord: {
enabled: true,
textChunkLimit: 1999,
maxLinesPerMessage: 17,
},
signal: { enabled: true, textChunkLimit: 2222 },
imessage: { enabled: true, textChunkLimit: 1111 },
},
});
expect(result.success).toBe(true);
if (!result.success) {
throw new Error(`expected schema parse success: ${JSON.stringify(result.error.issues)}`);
}
const cfg = result.data;
expect(cfg.channels?.whatsapp?.textChunkLimit).toBe(4444);
expect(cfg.channels?.telegram?.textChunkLimit).toBe(3333);
expect(cfg.channels?.discord?.textChunkLimit).toBe(1999);
expect(cfg.channels?.discord?.maxLinesPerMessage).toBe(17);
expect(cfg.channels?.signal?.textChunkLimit).toBe(2222);
expect(cfg.channels?.imessage?.textChunkLimit).toBe(1111);
const legacy = (cfg.messages as unknown as Record<string, unknown>).textChunkLimit;
expect(legacy).toBeUndefined();
});
it("accepts blank model provider apiKey values", () => {
const cfg = expectValidRawConfig({
models: {
mode: "merge",
providers: {
minimax: {
baseUrl: "https://api.minimax.io/anthropic",
apiKey: "",
api: "anthropic-messages",
models: [
{
id: "MiniMax-M2.7",
name: "MiniMax M2.7",
reasoning: false,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 200000,
maxTokens: 8192,
},
],
},
},
},
});
expect(cfg.models?.providers?.minimax?.baseUrl).toBe("https://api.minimax.io/anthropic");
expect(cfg.models?.providers?.minimax?.apiKey).toBe("");
});
it("accepts SecretRef values in model provider headers", () => {
const cfg = expectValidRawConfig({
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
api: "openai-completions",
headers: {
Authorization: {
source: "env",
provider: "default",
id: "OPENAI_HEADER_TOKEN",
},
},
models: [],
},
},
},
});
expect(cfg.models?.providers?.openai?.headers?.Authorization).toEqual({
source: "env",
provider: "default",
id: "OPENAI_HEADER_TOKEN",
});
});
it("respects empty responsePrefix to disable identity defaults", () => {
const cfg = expectValidConfig(configWithDefaultIdentity({ responsePrefix: "" }));
expect(cfg.messages?.responsePrefix).toBe("");
});
});

View File

@@ -171,6 +171,36 @@ describe("config secret refs schema", () => {
expect(result.ok).toBe(true);
});
it("accepts model provider header SecretRef values", () => {
const result = validateConfigObjectRaw({
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
api: "openai-completions",
headers: {
Authorization: {
source: "env",
provider: "default",
id: "OPENAI_HEADER_TOKEN",
},
},
models: [],
},
},
},
});
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.config.models?.providers?.openai?.headers?.Authorization).toEqual({
source: "env",
provider: "default",
id: "OPENAI_HEADER_TOKEN",
});
}
});
it("rejects model provider request proxy url secret refs", () => {
const result = validateConfigObjectRaw({
models: {

View File

@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
const mocks = vi.hoisted(() => ({
applyProviderConfigDefaultsWithPlugin: vi.fn(),
@@ -11,11 +12,14 @@ vi.mock("../plugins/provider-runtime.js", () => ({
}));
let applyContextPruningDefaults: typeof import("./defaults.js").applyContextPruningDefaults;
let applyAgentDefaults: typeof import("./defaults.js").applyAgentDefaults;
let applyMessageDefaults: typeof import("./defaults.js").applyMessageDefaults;
describe("config defaults", () => {
beforeEach(async () => {
vi.resetModules();
({ applyContextPruningDefaults } = await import("./defaults.js"));
({ applyAgentDefaults, applyContextPruningDefaults, applyMessageDefaults } =
await import("./defaults.js"));
mocks.applyProviderConfigDefaultsWithPlugin.mockReset();
});
@@ -54,4 +58,33 @@ describe("config defaults", () => {
expect(applyContextPruningDefaults(cfg as never)).toBe(nextCfg);
expect(mocks.applyProviderConfigDefaultsWithPlugin).toHaveBeenCalledTimes(1);
});
it("defaults ackReactionScope without deriving other message fields", () => {
const next = applyMessageDefaults({
agents: {
list: [
{
id: "main",
identity: {
name: "Samantha",
theme: "helpful sloth",
emoji: "🦥",
},
},
],
},
messages: {},
} as never);
expect(next.messages?.ackReactionScope).toBe("group-mentions");
expect(next.messages?.responsePrefix).toBeUndefined();
expect(next.messages?.groupChat?.mentionPatterns).toBeUndefined();
});
it("fills missing agent concurrency defaults", () => {
const next = applyAgentDefaults({ messages: {} } as never);
expect(next.agents?.defaults?.maxConcurrent).toBe(DEFAULT_AGENT_MAX_CONCURRENT);
expect(next.agents?.defaults?.subagents?.maxConcurrent).toBe(DEFAULT_SUBAGENT_MAX_CONCURRENT);
});
});