From 8134fe737c026d2a56fddac46caea390a3437679 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 20 Apr 2026 21:57:34 +0100 Subject: [PATCH] test(extensions): move legacy schema assertions --- extensions/discord/src/config-schema.test.ts | 12 ++ extensions/imessage/src/config-schema.test.ts | 57 +++++++ extensions/msteams/src/config-schema.test.ts | 18 +++ extensions/signal/src/config-schema.test.ts | 37 +++++ extensions/slack/src/config-schema.test.ts | 22 +++ extensions/telegram/src/config-schema.test.ts | 39 +++++ extensions/whatsapp/src/config-schema.test.ts | 44 ++++++ ...etection.accepts-imessage-dmpolicy.test.ts | 117 -------------- ...etection.rejects-routing-allowfrom.test.ts | 148 ------------------ src/config/plugin-auto-enable.core.test.ts | 6 +- src/config/validation.allowed-values.test.ts | 7 +- 11 files changed, 237 insertions(+), 270 deletions(-) diff --git a/extensions/discord/src/config-schema.test.ts b/extensions/discord/src/config-schema.test.ts index dfd2e57a96f..9d3560b6062 100644 --- a/extensions/discord/src/config-schema.test.ts +++ b/extensions/discord/src/config-schema.test.ts @@ -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, diff --git a/extensions/imessage/src/config-schema.test.ts b/extensions/imessage/src/config-schema.test.ts index 292ff314efb..afa864afc28 100644 --- a/extensions/imessage/src/config-schema.test.ts +++ b/extensions/imessage/src/config-schema.test.ts @@ -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, diff --git a/extensions/msteams/src/config-schema.test.ts b/extensions/msteams/src/config-schema.test.ts index 70eaa54cf36..1ce94c4c546 100644 --- a/extensions/msteams/src/config-schema.test.ts +++ b/extensions/msteams/src/config-schema.test.ts @@ -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", diff --git a/extensions/signal/src/config-schema.test.ts b/extensions/signal/src/config-schema.test.ts index f8017cc45d3..062de3275cd 100644 --- a/extensions/signal/src/config-schema.test.ts +++ b/extensions/signal/src/config-schema.test.ts @@ -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, diff --git a/extensions/slack/src/config-schema.test.ts b/extensions/slack/src/config-schema.test.ts index 6e044fd8d96..663f3980f7b 100644 --- a/extensions/slack/src/config-schema.test.ts +++ b/extensions/slack/src/config-schema.test.ts @@ -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( { diff --git a/extensions/telegram/src/config-schema.test.ts b/extensions/telegram/src/config-schema.test.ts index 961f9bc2642..cc843b65f0f 100644 --- a/extensions/telegram/src/config-schema.test.ts +++ b/extensions/telegram/src/config-schema.test.ts @@ -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, diff --git a/extensions/whatsapp/src/config-schema.test.ts b/extensions/whatsapp/src/config-schema.test.ts index e4d9def4050..ce1e089866e 100644 --- a/extensions/whatsapp/src/config-schema.test.ts +++ b/extensions/whatsapp/src/config-schema.test.ts @@ -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"], diff --git a/src/config/config.legacy-config-detection.accepts-imessage-dmpolicy.test.ts b/src/config/config.legacy-config-detection.accepts-imessage-dmpolicy.test.ts index 00605d13edf..7b3445efcdc 100644 --- a/src/config/config.legacy-config-detection.accepts-imessage-dmpolicy.test.ts +++ b/src/config/config.legacy-config-detection.accepts-imessage-dmpolicy.test.ts @@ -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" }, diff --git a/src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts b/src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts index e31a504ff77..2aff70735c2 100644 --- a/src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts +++ b/src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts @@ -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, - }); - }); }); diff --git a/src/config/plugin-auto-enable.core.test.ts b/src/config/plugin-auto-enable.core.test.ts index ea59734cd47..4c140ca02f5 100644 --- a/src/config/plugin-auto-enable.core.test.ts +++ b/src/config/plugin-auto-enable.core.test.ts @@ -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", () => { diff --git a/src/config/validation.allowed-values.test.ts b/src/config/validation.allowed-values.test.ts index f1cd52af8e5..db2757c829f 100644 --- a/src/config/validation.allowed-values.test.ts +++ b/src/config/validation.allowed-values.test.ts @@ -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:");