


























@@ -176,6 +176,27 @@ function createCompatPluginConfigSchemaRegistry(): PluginManifestRegistry {
176176};
177177}
178178179+function createDmPolicyRegistry(params: {
180+channelId: string;
181+dmAllowFromMode?: "topOnly" | "topOrNested" | "nestedOnly";
182+}): PluginManifestRegistry {
183+return {
184+diagnostics: [],
185+plugins: [
186+createPluginManifestRecord({
187+id: params.channelId,
188+channels: [params.channelId],
189+packageChannel: {
190+id: params.channelId,
191+ ...(params.dmAllowFromMode
192+ ? { doctorCapabilities: { dmAllowFromMode: params.dmAllowFromMode } }
193+ : {}),
194+},
195+}),
196+],
197+};
198+}
199+179200function createPluginManifestRecord(
180201overrides: Partial<PluginManifestRecord> & Pick<PluginManifestRecord, "id">,
181202): PluginManifestRecord {
@@ -416,6 +437,118 @@ describe("validateConfigObjectWithPlugins channel metadata (applyDefaults: true)
416437});
417438});
418439440+describe("validateConfigObjectWithPlugins DM policy warnings", () => {
441+it("uses manifest metadata to skip nested-only DM config shapes", () => {
442+const result = validateConfigObjectWithPlugins(
443+{
444+channels: {
445+matrix: {
446+dm: {
447+policy: "open",
448+},
449+},
450+},
451+},
452+{
453+pluginMetadataSnapshot: {
454+manifestRegistry: createDmPolicyRegistry({
455+channelId: "matrix",
456+dmAllowFromMode: "nestedOnly",
457+}),
458+},
459+},
460+);
461+462+expect(result.ok).toBe(true);
463+if (result.ok) {
464+expect(
465+result.warnings.filter((warning) => warning.path.startsWith("channels.matrix")),
466+).toEqual([]);
467+}
468+});
469+470+it("does not warn for disabled channels or accounts", () => {
471+const result = validateConfigObjectWithPlugins(
472+{
473+channels: {
474+mattermost: {
475+enabled: false,
476+dmPolicy: "open",
477+accounts: {
478+team: {
479+dmPolicy: "open",
480+},
481+},
482+},
483+slack: {
484+accounts: {
485+work: {
486+enabled: false,
487+dmPolicy: "open",
488+},
489+},
490+},
491+},
492+},
493+{
494+pluginMetadataSnapshot: {
495+manifestRegistry: {
496+diagnostics: [],
497+plugins: [
498+ ...createDmPolicyRegistry({ channelId: "mattermost" }).plugins,
499+ ...createDmPolicyRegistry({ channelId: "slack" }).plugins,
500+],
501+},
502+},
503+},
504+);
505+506+expect(result.ok).toBe(true);
507+if (result.ok) {
508+expect(
509+result.warnings.filter((warning) => warning.path.startsWith("channels.mattermost")),
510+).toEqual([]);
511+expect(
512+result.warnings.filter((warning) => warning.path.startsWith("channels.slack")),
513+).toEqual([]);
514+}
515+});
516+517+it("does not suggest channel allowFrom as sufficient when account allowFrom overrides it", () => {
518+const result = validateConfigObjectWithPlugins(
519+{
520+channels: {
521+mattermost: {
522+allowFrom: ["*"],
523+accounts: {
524+team: {
525+dmPolicy: "open",
526+allowFrom: [],
527+},
528+},
529+},
530+},
531+},
532+{
533+pluginMetadataSnapshot: {
534+manifestRegistry: createDmPolicyRegistry({ channelId: "mattermost" }),
535+},
536+},
537+);
538+539+expect(result.ok).toBe(true);
540+if (result.ok) {
541+const warning = result.warnings.find(
542+(entry) => entry.path === "channels.mattermost.accounts.team.allowFrom",
543+);
544+expect(warning?.message).toContain(
545+"remove channels.mattermost.accounts.team.allowFrom to inherit channels.mattermost.allowFrom",
546+);
547+expect(warning?.message).not.toContain("(or channels.mattermost.allowFrom)");
548+}
549+});
550+});
551+419552describe("validateConfigObjectRawWithPlugins channel metadata", () => {
420553it("still injects channel AJV defaults even in raw mode — persistence safety is handled by io.ts", () => {
421554// Channel and plugin AJV validation always runs with applyDefaults: true
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。