mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-14 10:41:23 +00:00
test: speed up legacy config tests
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
DiscordConfigSchema,
|
||||
MSTeamsConfigSchema,
|
||||
SlackConfigSchema,
|
||||
} from "./zod-schema.providers-core.js";
|
||||
|
||||
const { loadConfig, readConfigFileSnapshot, validateConfigObject } =
|
||||
await vi.importActual<typeof import("./config.js")>("./config.js");
|
||||
@@ -55,6 +60,20 @@ function expectValidConfigValue(params: {
|
||||
expect(params.readValue(res.config)).toBe(params.expectedValue);
|
||||
}
|
||||
|
||||
function expectSchemaConfigValue(params: {
|
||||
schema: { safeParse: (value: unknown) => { success: true; data: unknown } | { success: false } };
|
||||
config: unknown;
|
||||
readValue: (config: unknown) => unknown;
|
||||
expectedValue: unknown;
|
||||
}) {
|
||||
const res = params.schema.safeParse(params.config);
|
||||
expect(res.success).toBe(true);
|
||||
if (!res.success) {
|
||||
throw new Error("expected schema config to be valid");
|
||||
}
|
||||
expect(params.readValue(res.data)).toBe(params.expectedValue);
|
||||
}
|
||||
|
||||
function expectInvalidIssuePath(config: unknown, expectedPath: string) {
|
||||
const res = validateConfigObject(config);
|
||||
expect(res.ok).toBe(false);
|
||||
@@ -84,49 +103,48 @@ describe("legacy config detection", () => {
|
||||
expect(res.config.channels?.imessage?.dmPolicy).toBe("open");
|
||||
}
|
||||
});
|
||||
it.each([
|
||||
[
|
||||
"defaults imessage.dmPolicy to pairing when imessage section exists",
|
||||
{ channels: { imessage: {} } },
|
||||
(config: unknown) =>
|
||||
it("defaults imessage.dmPolicy to pairing when imessage section exists", () => {
|
||||
expectValidConfigValue({
|
||||
config: { channels: { imessage: {} } },
|
||||
readValue: (config) =>
|
||||
(config as { channels?: { imessage?: { dmPolicy?: string } } }).channels?.imessage
|
||||
?.dmPolicy,
|
||||
"pairing",
|
||||
],
|
||||
[
|
||||
"defaults imessage.groupPolicy to allowlist when imessage section exists",
|
||||
{ channels: { imessage: {} } },
|
||||
(config: unknown) =>
|
||||
expectedValue: "pairing",
|
||||
});
|
||||
});
|
||||
it("defaults imessage.groupPolicy to allowlist when imessage section exists", () => {
|
||||
expectValidConfigValue({
|
||||
config: { channels: { imessage: {} } },
|
||||
readValue: (config) =>
|
||||
(config as { channels?: { imessage?: { groupPolicy?: string } } }).channels?.imessage
|
||||
?.groupPolicy,
|
||||
"allowlist",
|
||||
],
|
||||
expectedValue: "allowlist",
|
||||
});
|
||||
});
|
||||
it.each([
|
||||
[
|
||||
"defaults discord.groupPolicy to allowlist when discord section exists",
|
||||
{ channels: { discord: {} } },
|
||||
(config: unknown) =>
|
||||
(config as { channels?: { discord?: { groupPolicy?: string } } }).channels?.discord
|
||||
?.groupPolicy,
|
||||
DiscordConfigSchema,
|
||||
{},
|
||||
(config: unknown) => (config as { groupPolicy?: string }).groupPolicy,
|
||||
"allowlist",
|
||||
],
|
||||
[
|
||||
"defaults slack.groupPolicy to allowlist when slack section exists",
|
||||
{ channels: { slack: {} } },
|
||||
(config: unknown) =>
|
||||
(config as { channels?: { slack?: { groupPolicy?: string } } }).channels?.slack
|
||||
?.groupPolicy,
|
||||
SlackConfigSchema,
|
||||
{},
|
||||
(config: unknown) => (config as { groupPolicy?: string }).groupPolicy,
|
||||
"allowlist",
|
||||
],
|
||||
[
|
||||
"defaults msteams.groupPolicy to allowlist when msteams section exists",
|
||||
{ channels: { msteams: {} } },
|
||||
(config: unknown) =>
|
||||
(config as { channels?: { msteams?: { groupPolicy?: string } } }).channels?.msteams
|
||||
?.groupPolicy,
|
||||
MSTeamsConfigSchema,
|
||||
{},
|
||||
(config: unknown) => (config as { groupPolicy?: string }).groupPolicy,
|
||||
"allowlist",
|
||||
],
|
||||
])("defaults: %s", (_name, config, readValue, expectedValue) => {
|
||||
expectValidConfigValue({ config, readValue, expectedValue });
|
||||
])("defaults: %s", (_name, schema, config, readValue, expectedValue) => {
|
||||
expectSchemaConfigValue({ schema, config, readValue, expectedValue });
|
||||
});
|
||||
it("rejects unsafe executable config values", async () => {
|
||||
const res = validateConfigObject({
|
||||
@@ -207,47 +225,6 @@ describe("legacy config detection", () => {
|
||||
expect(res.issues[0]?.message).toContain('"agent"');
|
||||
}
|
||||
});
|
||||
it("flags channels.telegram.groupMentionsOnly as legacy in snapshot", async () => {
|
||||
await withSnapshotForConfig(
|
||||
{ channels: { telegram: { groupMentionsOnly: true } } },
|
||||
async (ctx) => {
|
||||
expect(ctx.snapshot.valid).toBe(true);
|
||||
expect(
|
||||
ctx.snapshot.legacyIssues.some(
|
||||
(issue) => issue.path === "channels.telegram.groupMentionsOnly",
|
||||
),
|
||||
).toBe(true);
|
||||
const parsed = ctx.parsed as {
|
||||
channels?: { telegram?: { groupMentionsOnly?: boolean } };
|
||||
};
|
||||
expect(parsed.channels?.telegram?.groupMentionsOnly).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects removed routing.allowFrom in snapshot", async () => {
|
||||
await withSnapshotForConfig({ routing: { allowFrom: ["+15555550123"] } }, async (ctx) => {
|
||||
expectSnapshotInvalidRootKey(ctx, "routing");
|
||||
});
|
||||
});
|
||||
it("flags top-level memorySearch as legacy in snapshot", async () => {
|
||||
await withSnapshotForConfig(
|
||||
{ memorySearch: { provider: "local", fallback: "none" } },
|
||||
async (ctx) => {
|
||||
expect(ctx.snapshot.valid).toBe(true);
|
||||
expect(ctx.snapshot.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
it("flags top-level heartbeat as legacy in snapshot", async () => {
|
||||
await withSnapshotForConfig(
|
||||
{ heartbeat: { model: "anthropic/claude-3-5-haiku-20241022", every: "30m" } },
|
||||
async (ctx) => {
|
||||
expect(ctx.snapshot.valid).toBe(true);
|
||||
expect(ctx.snapshot.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
it("rejects removed legacy provider sections in snapshot", async () => {
|
||||
await withSnapshotForConfig({ whatsapp: { allowFrom: ["+1555"] } }, async (ctx) => {
|
||||
expectSnapshotInvalidRootKey(ctx, "whatsapp");
|
||||
@@ -283,20 +260,6 @@ describe("legacy config detection", () => {
|
||||
expect(parsed.auth?.profiles?.["anthropic:claude-cli"]?.mode).toBe("token");
|
||||
});
|
||||
});
|
||||
it("still flags memorySearch in snapshot under the shorter support window", async () => {
|
||||
await withSnapshotForConfig(
|
||||
{ memorySearch: { provider: "local", fallback: "none" } },
|
||||
async (ctx) => {
|
||||
expect(ctx.snapshot.valid).toBe(true);
|
||||
expect(ctx.snapshot.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
it("rejects removed routing.allowFrom in snapshot with other values", async () => {
|
||||
await withSnapshotForConfig({ routing: { allowFrom: ["+1666"] } }, async (ctx) => {
|
||||
expectSnapshotInvalidRootKey(ctx, "routing");
|
||||
});
|
||||
});
|
||||
it("rejects bindings[].match.provider on load", async () => {
|
||||
await expectLoadRejectionPreservesField({
|
||||
config: {
|
||||
|
||||
@@ -1,18 +1,46 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyLegacyDoctorMigrations } from "../commands/doctor/shared/legacy-config-migrate.js";
|
||||
import type { OpenClawConfig } from "./config.js";
|
||||
import { IMessageConfigSchema } from "../../extensions/imessage/config-api.js";
|
||||
import { SignalConfigSchema } from "../../extensions/signal/config-api.js";
|
||||
import { TelegramConfigSchema } from "../../extensions/telegram/config-api.js";
|
||||
import { WhatsAppConfigSchema } from "../../extensions/whatsapp/config-api.js";
|
||||
import { findLegacyConfigIssues } from "./legacy.js";
|
||||
import { validateConfigObject } from "./validation.js";
|
||||
import {
|
||||
DiscordConfigSchema,
|
||||
MSTeamsConfigSchema,
|
||||
SlackConfigSchema,
|
||||
} from "./zod-schema.providers-core.js";
|
||||
|
||||
function getChannelConfig(config: unknown, provider: string) {
|
||||
const channels = (config as { channels?: Record<string, Record<string, unknown>> } | undefined)
|
||||
?.channels;
|
||||
return channels?.[provider];
|
||||
function expectSchemaInvalidIssuePath(
|
||||
schema: {
|
||||
safeParse: (
|
||||
value: unknown,
|
||||
) =>
|
||||
| { success: true }
|
||||
| { success: false; error: { issues: Array<{ path?: Array<string | number> }> } };
|
||||
},
|
||||
config: unknown,
|
||||
expectedPath: string,
|
||||
) {
|
||||
const res = schema.safeParse(config);
|
||||
expect(res.success).toBe(false);
|
||||
if (!res.success) {
|
||||
expect(res.error.issues[0]?.path?.join(".")).toBe(expectedPath);
|
||||
}
|
||||
}
|
||||
|
||||
function expectMigratedConfig(input: unknown, name: string) {
|
||||
const migrated = applyLegacyDoctorMigrations(input);
|
||||
expect(migrated.next, name).not.toBeNull();
|
||||
return migrated.next as NonNullable<OpenClawConfig>;
|
||||
function expectSchemaConfigValue(params: {
|
||||
schema: { safeParse: (value: unknown) => { success: true; data: unknown } | { success: false } };
|
||||
config: unknown;
|
||||
readValue: (config: unknown) => unknown;
|
||||
expectedValue: unknown;
|
||||
}) {
|
||||
const res = params.schema.safeParse(params.config);
|
||||
expect(res.success).toBe(true);
|
||||
if (!res.success) {
|
||||
throw new Error("expected schema config to be valid");
|
||||
}
|
||||
expect(params.readValue(res.data)).toBe(params.expectedValue);
|
||||
}
|
||||
|
||||
describe("legacy config detection", () => {
|
||||
@@ -82,15 +110,10 @@ describe("legacy config detection", () => {
|
||||
}
|
||||
});
|
||||
it("rejects channels.telegram.groupMentionsOnly", async () => {
|
||||
const res = validateConfigObject({
|
||||
const issues = findLegacyConfigIssues({
|
||||
channels: { telegram: { groupMentionsOnly: true } },
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.issues.some((issue) => issue.path === "channels.telegram.groupMentionsOnly")).toBe(
|
||||
true,
|
||||
);
|
||||
}
|
||||
expect(issues.some((issue) => issue.path === "channels.telegram.groupMentionsOnly")).toBe(true);
|
||||
});
|
||||
it("rejects gateway.token", async () => {
|
||||
const res = validateConfigObject({
|
||||
@@ -116,265 +139,144 @@ describe("legacy config detection", () => {
|
||||
);
|
||||
it.each([
|
||||
{
|
||||
provider: "telegram",
|
||||
name: "telegram",
|
||||
schema: TelegramConfigSchema,
|
||||
allowFrom: ["123456789"],
|
||||
expectedIssuePath: "channels.telegram.allowFrom",
|
||||
expectedIssuePath: "allowFrom",
|
||||
},
|
||||
{
|
||||
provider: "whatsapp",
|
||||
name: "whatsapp",
|
||||
schema: WhatsAppConfigSchema,
|
||||
allowFrom: ["+15555550123"],
|
||||
expectedIssuePath: "channels.whatsapp.allowFrom",
|
||||
expectedIssuePath: "allowFrom",
|
||||
},
|
||||
{
|
||||
provider: "signal",
|
||||
name: "signal",
|
||||
schema: SignalConfigSchema,
|
||||
allowFrom: ["+15555550123"],
|
||||
expectedIssuePath: "channels.signal.allowFrom",
|
||||
expectedIssuePath: "allowFrom",
|
||||
},
|
||||
{
|
||||
provider: "imessage",
|
||||
name: "imessage",
|
||||
schema: IMessageConfigSchema,
|
||||
allowFrom: ["+15555550123"],
|
||||
expectedIssuePath: "channels.imessage.allowFrom",
|
||||
expectedIssuePath: "allowFrom",
|
||||
},
|
||||
] as const)(
|
||||
'enforces dmPolicy="open" allowFrom wildcard for $provider',
|
||||
({ provider, allowFrom, expectedIssuePath }) => {
|
||||
'enforces dmPolicy="open" allowFrom wildcard for $name',
|
||||
({ name, schema, allowFrom, expectedIssuePath }) => {
|
||||
if (schema) {
|
||||
expectSchemaInvalidIssuePath(schema, { dmPolicy: "open", allowFrom }, expectedIssuePath);
|
||||
return;
|
||||
}
|
||||
const res = validateConfigObject({
|
||||
channels: {
|
||||
[provider]: { dmPolicy: "open", allowFrom },
|
||||
[name]: { dmPolicy: "open", allowFrom },
|
||||
},
|
||||
});
|
||||
expect(res.ok, provider).toBe(false);
|
||||
expect(res.ok, name).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.issues[0]?.path, provider).toBe(expectedIssuePath);
|
||||
expect(res.issues[0]?.path, name).toBe(expectedIssuePath);
|
||||
}
|
||||
},
|
||||
180_000,
|
||||
);
|
||||
|
||||
it.each(["telegram", "whatsapp", "signal"] as const)(
|
||||
'accepts dmPolicy="open" with wildcard for %s',
|
||||
(provider) => {
|
||||
const res = validateConfigObject({
|
||||
channels: { [provider]: { dmPolicy: "open", allowFrom: ["*"] } },
|
||||
});
|
||||
expect(res.ok, provider).toBe(true);
|
||||
if (res.ok) {
|
||||
const channel = getChannelConfig(res.config, provider);
|
||||
expect(channel?.dmPolicy, provider).toBe("open");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["telegram", "whatsapp", "signal"] as const)(
|
||||
"defaults dm/group policy for configured provider %s",
|
||||
(provider) => {
|
||||
const res = validateConfigObject({ channels: { [provider]: {} } });
|
||||
expect(res.ok, provider).toBe(true);
|
||||
if (res.ok) {
|
||||
const channel = getChannelConfig(res.config, provider);
|
||||
expect(channel?.dmPolicy, provider).toBe("pairing");
|
||||
expect(channel?.groupPolicy, provider).toBe("allowlist");
|
||||
}
|
||||
},
|
||||
);
|
||||
it.each([
|
||||
{
|
||||
name: "top-level off",
|
||||
input: { channels: { telegram: { streamMode: "off" } } },
|
||||
assert: (config: NonNullable<OpenClawConfig>) => {
|
||||
expect(config.channels?.telegram?.streaming?.mode).toBe("off");
|
||||
expect(
|
||||
(config.channels?.telegram as Record<string, unknown> | undefined)?.streamMode,
|
||||
).toBeUndefined();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "top-level block",
|
||||
input: { channels: { telegram: { streamMode: "block" } } },
|
||||
assert: (config: NonNullable<OpenClawConfig>) => {
|
||||
expect(config.channels?.telegram?.streaming?.mode).toBe("block");
|
||||
expect(
|
||||
(config.channels?.telegram as Record<string, unknown> | undefined)?.streamMode,
|
||||
).toBeUndefined();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "per-account off",
|
||||
input: {
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
ops: {
|
||||
streamMode: "off",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
assert: (config: NonNullable<OpenClawConfig>) => {
|
||||
expect(config.channels?.telegram?.accounts?.ops?.streaming?.mode).toBe("off");
|
||||
expect(
|
||||
(config.channels?.telegram?.accounts?.ops as Record<string, unknown> | undefined)
|
||||
?.streamMode,
|
||||
).toBeUndefined();
|
||||
},
|
||||
},
|
||||
] as const)(
|
||||
"normalizes telegram legacy streamMode alias during migration: $name",
|
||||
({ input, assert, name }) => {
|
||||
assert(expectMigratedConfig(input, name));
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "boolean streaming=true",
|
||||
input: { channels: { discord: { streaming: true } } },
|
||||
expectedChanges: [
|
||||
"Moved channels.discord.streaming (boolean) → channels.discord.streaming.mode (partial).",
|
||||
],
|
||||
expectedStreaming: "partial",
|
||||
},
|
||||
{
|
||||
name: "streamMode with streaming boolean",
|
||||
input: { channels: { discord: { streaming: false, streamMode: "block" } } },
|
||||
expectedChanges: [
|
||||
"Moved channels.discord.streamMode → channels.discord.streaming.mode (block).",
|
||||
],
|
||||
expectedStreaming: "block",
|
||||
},
|
||||
] as const)(
|
||||
"normalizes discord streaming fields during legacy migration: $name",
|
||||
({ input, expectedChanges, expectedStreaming, name }) => {
|
||||
const migrated = applyLegacyDoctorMigrations(input);
|
||||
for (const expectedChange of expectedChanges) {
|
||||
expect(migrated.changes, name).toContain(expectedChange);
|
||||
}
|
||||
const config = migrated.next as NonNullable<OpenClawConfig> | null;
|
||||
expect(config, name).not.toBeNull();
|
||||
expect(config?.channels?.discord?.streaming?.mode, name).toBe(expectedStreaming);
|
||||
expect(
|
||||
(config?.channels?.discord as Record<string, unknown> | undefined)?.streamMode,
|
||||
name,
|
||||
).toBeUndefined();
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "streaming=true",
|
||||
input: { channels: { discord: { streaming: true } } },
|
||||
expectedStreaming: "partial",
|
||||
},
|
||||
{
|
||||
name: "streaming=false",
|
||||
input: { channels: { discord: { streaming: false } } },
|
||||
expectedStreaming: "off",
|
||||
},
|
||||
{
|
||||
name: "streamMode overrides streaming boolean",
|
||||
input: { channels: { discord: { streamMode: "block", streaming: false } } },
|
||||
expectedStreaming: "block",
|
||||
},
|
||||
] as const)(
|
||||
"rejects legacy discord streaming fields during validation: $name",
|
||||
({ input, name }) => {
|
||||
const res = validateConfigObject(input);
|
||||
expect(res.ok, name).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.issues[0]?.path, name).toBe("channels.discord");
|
||||
expect(res.issues[0]?.message, name).toContain(
|
||||
"channels.discord.streamMode, channels.discord.streaming (scalar), chunkMode, blockStreaming, draftChunk, and blockStreamingCoalesce are legacy",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "discord account streaming boolean",
|
||||
input: {
|
||||
channels: {
|
||||
discord: {
|
||||
accounts: {
|
||||
work: {
|
||||
streaming: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
assert: (config: NonNullable<OpenClawConfig>) => {
|
||||
expect(config.channels?.discord?.accounts?.work?.streaming?.mode).toBe("partial");
|
||||
expect(
|
||||
(config.channels?.discord?.accounts?.work as Record<string, unknown> | undefined)
|
||||
?.streamMode,
|
||||
).toBeUndefined();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "slack streamMode alias",
|
||||
input: {
|
||||
channels: {
|
||||
slack: {
|
||||
streamMode: "status_final",
|
||||
},
|
||||
},
|
||||
},
|
||||
assert: (config: NonNullable<OpenClawConfig>) => {
|
||||
expect(config.channels?.slack?.streaming?.mode).toBe("progress");
|
||||
expect(
|
||||
(config.channels?.slack as Record<string, unknown> | undefined)?.streamMode,
|
||||
).toBeUndefined();
|
||||
expect(config.channels?.slack?.streaming?.nativeTransport).toBeUndefined();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "slack streaming boolean legacy",
|
||||
input: {
|
||||
channels: {
|
||||
slack: {
|
||||
streaming: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
assert: (config: NonNullable<OpenClawConfig>) => {
|
||||
expect(config.channels?.slack?.streaming?.mode).toBe("off");
|
||||
expect(config.channels?.slack?.streaming?.nativeTransport).toBe(false);
|
||||
},
|
||||
},
|
||||
] as const)(
|
||||
"normalizes account-level discord/slack streaming alias during migration: $name",
|
||||
({ input, assert, name }) => {
|
||||
assert(expectMigratedConfig(input, name));
|
||||
},
|
||||
);
|
||||
|
||||
it("accepts historyLimit overrides per provider and account", async () => {
|
||||
const res = validateConfigObject({
|
||||
messages: { groupChat: { historyLimit: 12 } },
|
||||
channels: {
|
||||
whatsapp: { historyLimit: 9, accounts: { work: { historyLimit: 4 } } },
|
||||
telegram: { historyLimit: 8, accounts: { ops: { historyLimit: 3 } } },
|
||||
slack: { historyLimit: 7, accounts: { ops: { historyLimit: 2 } } },
|
||||
signal: { historyLimit: 6 },
|
||||
imessage: { historyLimit: 5 },
|
||||
msteams: { historyLimit: 4 },
|
||||
discord: { historyLimit: 3 },
|
||||
},
|
||||
["telegram", TelegramConfigSchema],
|
||||
["whatsapp", WhatsAppConfigSchema],
|
||||
["signal", SignalConfigSchema],
|
||||
] as const)('accepts dmPolicy="open" with wildcard for %s', (provider, schema) => {
|
||||
expectSchemaConfigValue({
|
||||
schema,
|
||||
config: { dmPolicy: "open", allowFrom: ["*"] },
|
||||
readValue: (config) => (config as { dmPolicy?: string }).dmPolicy,
|
||||
expectedValue: "open",
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["telegram", TelegramConfigSchema],
|
||||
["whatsapp", WhatsAppConfigSchema],
|
||||
["signal", SignalConfigSchema],
|
||||
] as const)("defaults dm/group policy for configured provider %s", (provider, 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,
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
if (res.ok) {
|
||||
expect(res.config.channels?.whatsapp?.historyLimit).toBe(9);
|
||||
expect(res.config.channels?.whatsapp?.accounts?.work?.historyLimit).toBe(4);
|
||||
expect(res.config.channels?.telegram?.historyLimit).toBe(8);
|
||||
expect(res.config.channels?.telegram?.accounts?.ops?.historyLimit).toBe(3);
|
||||
expect(res.config.channels?.slack?.historyLimit).toBe(7);
|
||||
expect(res.config.channels?.slack?.accounts?.ops?.historyLimit).toBe(2);
|
||||
expect(res.config.channels?.signal?.historyLimit).toBe(6);
|
||||
expect(res.config.channels?.imessage?.historyLimit).toBe(5);
|
||||
expect(res.config.channels?.msteams?.historyLimit).toBe(4);
|
||||
expect(res.config.channels?.discord?.historyLimit).toBe(3);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,175 +35,4 @@ describe("legacy provider-shaped config snapshots", () => {
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts legacy messages.tts provider keys via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
messages: {
|
||||
tts: {
|
||||
provider: "elevenlabs",
|
||||
elevenlabs: {
|
||||
apiKey: "test-key",
|
||||
voiceId: "voice-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "messages.tts")).toBe(true);
|
||||
expect(snap.sourceConfig.messages?.tts).toEqual({
|
||||
provider: "elevenlabs",
|
||||
providers: {
|
||||
elevenlabs: {
|
||||
apiKey: "test-key",
|
||||
voiceId: "voice-1",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.messages?.tts as Record<string, unknown> | undefined)?.elevenlabs,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy talk flat fields via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
talk: {
|
||||
voiceId: "voice-1",
|
||||
modelId: "eleven_v3",
|
||||
apiKey: "test-key",
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "talk")).toBe(true);
|
||||
expect(snap.sourceConfig.talk?.providers?.elevenlabs).toEqual({
|
||||
voiceId: "voice-1",
|
||||
modelId: "eleven_v3",
|
||||
apiKey: "test-key",
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.talk as Record<string, unknown> | undefined)?.voiceId,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(snap.sourceConfig.talk as Record<string, unknown> | undefined)?.modelId,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(snap.sourceConfig.talk as Record<string, unknown> | undefined)?.apiKey,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy plugins.entries.*.config.tts provider keys via auto-migration", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
plugins: {
|
||||
entries: {
|
||||
"voice-call": {
|
||||
config: {
|
||||
tts: {
|
||||
provider: "openai",
|
||||
openai: {
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "alloy",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "plugins.entries")).toBe(true);
|
||||
const voiceCallTts = (
|
||||
snap.sourceConfig.plugins?.entries as
|
||||
| Record<
|
||||
string,
|
||||
{
|
||||
config?: {
|
||||
tts?: {
|
||||
providers?: Record<string, unknown>;
|
||||
openai?: unknown;
|
||||
};
|
||||
};
|
||||
}
|
||||
>
|
||||
| undefined
|
||||
)?.["voice-call"]?.config?.tts;
|
||||
expect(voiceCallTts).toEqual({
|
||||
provider: "openai",
|
||||
providers: {
|
||||
openai: {
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "alloy",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(voiceCallTts?.openai).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy discord voice tts provider keys via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
channels: {
|
||||
discord: {
|
||||
voice: {
|
||||
tts: {
|
||||
provider: "elevenlabs",
|
||||
elevenlabs: {
|
||||
voiceId: "voice-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
main: {
|
||||
voice: {
|
||||
tts: {
|
||||
edge: {
|
||||
voice: "en-US-AvaNeural",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.voice.tts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.sourceConfig.channels?.discord?.voice?.tts).toEqual({
|
||||
provider: "elevenlabs",
|
||||
providers: {
|
||||
elevenlabs: {
|
||||
voiceId: "voice-1",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(snap.sourceConfig.channels?.discord?.accounts?.main?.voice?.tts).toEqual({
|
||||
providers: {
|
||||
microsoft: {
|
||||
voice: "en-US-AvaNeural",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,366 +38,6 @@ describe("config strict validation", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("detects top-level memorySearch and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
memorySearch: {
|
||||
provider: "local",
|
||||
fallback: "none",
|
||||
query: { maxResults: 7 },
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true);
|
||||
expect(snap.sourceConfig.agents?.defaults?.memorySearch).toMatchObject({
|
||||
provider: "local",
|
||||
fallback: "none",
|
||||
query: { maxResults: 7 },
|
||||
});
|
||||
expect((snap.sourceConfig as { memorySearch?: unknown }).memorySearch).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("detects top-level heartbeat agent settings and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
heartbeat: {
|
||||
every: "30m",
|
||||
model: "anthropic/claude-3-5-haiku-20241022",
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true);
|
||||
expect(snap.sourceConfig.agents?.defaults?.heartbeat).toMatchObject({
|
||||
every: "30m",
|
||||
model: "anthropic/claude-3-5-haiku-20241022",
|
||||
});
|
||||
expect((snap.sourceConfig as { heartbeat?: unknown }).heartbeat).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("detects top-level heartbeat visibility and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
heartbeat: {
|
||||
showOk: true,
|
||||
showAlerts: false,
|
||||
useIndicator: true,
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true);
|
||||
expect(snap.sourceConfig.channels?.defaults?.heartbeat).toMatchObject({
|
||||
showOk: true,
|
||||
showAlerts: false,
|
||||
useIndicator: true,
|
||||
});
|
||||
expect((snap.sourceConfig as { heartbeat?: unknown }).heartbeat).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("detects legacy sandbox perSession and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
perSession: true,
|
||||
},
|
||||
},
|
||||
list: [
|
||||
{
|
||||
id: "pi",
|
||||
sandbox: {
|
||||
perSession: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "agents.defaults.sandbox")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "agents.list")).toBe(true);
|
||||
expect(snap.sourceConfig.agents?.defaults?.sandbox).toEqual({ scope: "session" });
|
||||
expect(snap.sourceConfig.agents?.list?.[0]?.sandbox).toEqual({ scope: "shared" });
|
||||
});
|
||||
});
|
||||
|
||||
it("detects legacy x_search auth and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
tools: {
|
||||
web: {
|
||||
x_search: {
|
||||
apiKey: "test-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "tools.web.x_search.apiKey")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.sourceConfig.plugins?.entries?.xai?.enabled).toBe(true);
|
||||
expect(snap.sourceConfig.plugins?.entries?.xai?.config?.webSearch).toMatchObject({
|
||||
apiKey: "test-key",
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.tools?.web?.x_search as Record<string, unknown> | undefined)?.apiKey,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("detects legacy thread binding ttlHours and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
session: {
|
||||
threadBindings: {
|
||||
ttlHours: 24,
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
discord: {
|
||||
threadBindings: {
|
||||
ttlHours: 12,
|
||||
},
|
||||
accounts: {
|
||||
alpha: {
|
||||
threadBindings: {
|
||||
ttlHours: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "session.threadBindings")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels")).toBe(true);
|
||||
expect(snap.sourceConfig.session?.threadBindings).toMatchObject({ idleHours: 24 });
|
||||
expect(snap.sourceConfig.channels?.discord?.threadBindings).toMatchObject({ idleHours: 12 });
|
||||
expect(snap.sourceConfig.channels?.discord?.accounts?.alpha?.threadBindings).toMatchObject({
|
||||
idleHours: 6,
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.session?.threadBindings as Record<string, unknown> | undefined)
|
||||
?.ttlHours,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("detects legacy channel streaming aliases and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
channels: {
|
||||
telegram: {
|
||||
streamMode: "block",
|
||||
},
|
||||
discord: {
|
||||
streaming: false,
|
||||
accounts: {
|
||||
work: {
|
||||
streamMode: "block",
|
||||
},
|
||||
},
|
||||
},
|
||||
googlechat: {
|
||||
streamMode: "append",
|
||||
accounts: {
|
||||
work: {
|
||||
streamMode: "replace",
|
||||
},
|
||||
},
|
||||
},
|
||||
slack: {
|
||||
streaming: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.telegram")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.googlechat")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.googlechat.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack")).toBe(true);
|
||||
expect(snap.sourceConfig.channels?.telegram).toMatchObject({
|
||||
streaming: {
|
||||
mode: "block",
|
||||
},
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.channels?.telegram as Record<string, unknown> | undefined)?.streamMode,
|
||||
).toBeUndefined();
|
||||
expect(snap.sourceConfig.channels?.discord).toMatchObject({
|
||||
streaming: {
|
||||
mode: "off",
|
||||
},
|
||||
});
|
||||
expect(snap.sourceConfig.channels?.discord?.accounts?.work).toMatchObject({
|
||||
streaming: {
|
||||
mode: "block",
|
||||
},
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.channels?.googlechat as Record<string, unknown> | undefined)?.streamMode,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(
|
||||
snap.sourceConfig.channels?.googlechat?.accounts?.work as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
)?.streamMode,
|
||||
).toBeUndefined();
|
||||
expect(snap.sourceConfig.channels?.slack).toMatchObject({
|
||||
streaming: {
|
||||
mode: "partial",
|
||||
nativeTransport: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("detects legacy nested channel allow aliases and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
channels: {
|
||||
slack: {
|
||||
channels: {
|
||||
ops: {
|
||||
allow: false,
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
channels: {
|
||||
general: {
|
||||
allow: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
googlechat: {
|
||||
groups: {
|
||||
"spaces/aaa": {
|
||||
allow: false,
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
groups: {
|
||||
"spaces/bbb": {
|
||||
allow: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
discord: {
|
||||
guilds: {
|
||||
"100": {
|
||||
channels: {
|
||||
general: {
|
||||
allow: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
guilds: {
|
||||
"200": {
|
||||
channels: {
|
||||
help: {
|
||||
allow: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.googlechat")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.googlechat.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.sourceConfig.channels?.slack?.channels?.ops).toMatchObject({ enabled: false });
|
||||
expect(snap.sourceConfig.channels?.googlechat?.groups?.["spaces/aaa"]).toMatchObject({
|
||||
enabled: false,
|
||||
});
|
||||
expect(snap.sourceConfig.channels?.discord?.guilds?.["100"]?.channels?.general).toMatchObject(
|
||||
{ enabled: false },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("detects telegram groupMentionsOnly and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
channels: {
|
||||
telegram: {
|
||||
groupMentionsOnly: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(
|
||||
snap.legacyIssues.some((issue) => issue.path === "channels.telegram.groupMentionsOnly"),
|
||||
).toBe(true);
|
||||
expect(snap.sourceConfig.channels?.telegram?.groups?.["*"]).toMatchObject({
|
||||
requireMention: true,
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.channels?.telegram as Record<string, unknown> | undefined)
|
||||
?.groupMentionsOnly,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not treat resolved-only gateway.bind aliases as source-literal legacy or invalid", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
@@ -420,16 +60,4 @@ describe("config strict validation", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("still marks literal gateway.bind host aliases as legacy", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
gateway: { bind: "0.0.0.0" },
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "gateway.bind")).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user