




























@@ -244,9 +244,9 @@ function expectCandidateIds(
244244params: { includes?: readonly string[]; excludes?: readonly string[] },
245245) {
246246const ids = candidates.map((candidate) => candidate.idHint);
247-if (params.includes?.length) {
248-expect(ids).toEqual(expect.arrayContaining([...params.includes]));
249-}
247+params.includes?.forEach((includedId) => {
248+expect(ids).toContain(includedId);
249+});
250250params.excludes?.forEach((excludedId) => {
251251expect(ids).not.toContain(excludedId);
252252});
@@ -273,9 +273,72 @@ function expectCandidateSource(
273273}
274274275275function expectEscapesPackageDiagnostic(diagnostics: Array<{ message: string }>) {
276-expect(diagnostics.map((entry) => entry.message)).toEqual(
277-expect.arrayContaining([expect.stringContaining("escapes package directory")]),
276+expect(diagnostics.some((entry) => entry.message.includes("escapes package directory"))).toBe(
277+true,
278+);
279+}
280+281+function expectDiagnostic(params: {
282+diagnostics: Array<{
283+level?: string;
284+message: string;
285+pluginId?: string;
286+source?: string;
287+}>;
288+messageIncludes: string;
289+level?: string;
290+pluginId?: string;
291+source?: string;
292+}) {
293+const matched = params.diagnostics.some(
294+(diagnostic) =>
295+diagnostic.message.includes(params.messageIncludes) &&
296+(params.level === undefined || diagnostic.level === params.level) &&
297+(params.pluginId === undefined || diagnostic.pluginId === params.pluginId) &&
298+(params.source === undefined || diagnostic.source === params.source),
299+);
300+expect(matched).toBe(true);
301+}
302+303+function expectNoDiagnostic(params: {
304+diagnostics: Array<{
305+message: string;
306+pluginId?: string;
307+source?: string;
308+}>;
309+messageIncludes: string;
310+pluginId?: string;
311+source?: string;
312+}) {
313+const matched = params.diagnostics.some(
314+(diagnostic) =>
315+diagnostic.message.includes(params.messageIncludes) &&
316+(params.pluginId === undefined || diagnostic.pluginId === params.pluginId) &&
317+(params.source === undefined || diagnostic.source === params.source),
278318);
319+expect(matched).toBe(false);
320+}
321+322+function expectCandidateFields(
323+candidate:
324+| {
325+idHint?: string;
326+format?: string;
327+bundleFormat?: string;
328+source?: string;
329+rootDir?: string;
330+origin?: string;
331+}
332+| undefined,
333+expected: Record<string, unknown>,
334+) {
335+expect(candidate).toBeDefined();
336+if (!candidate) {
337+throw new Error("Expected plugin candidate");
338+}
339+for (const [key, value] of Object.entries(expected)) {
340+expect(candidate[key as keyof typeof candidate], key).toBe(value);
341+}
279342}
280343281344function expectCandidatePresence(
@@ -312,14 +375,12 @@ function expectBundleCandidateMatch(params: {
312375expectRootDir?: boolean;
313376}) {
314377const bundle = requireCandidateById(params.candidates, params.idHint);
315-expect(bundle).toEqual(
316-expect.objectContaining({
317-idHint: params.idHint,
318-format: "bundle",
319-bundleFormat: params.bundleFormat,
320-source: params.source,
321-}),
322-);
378+expectCandidateFields(bundle, {
379+idHint: params.idHint,
380+format: "bundle",
381+bundleFormat: params.bundleFormat,
382+source: params.source,
383+});
323384if (params.expectRootDir) {
324385expect(normalizePathForAssertion(bundle?.rootDir)).toBe(
325386normalizePathForAssertion(fs.realpathSync(params.source)),
@@ -601,17 +662,15 @@ describe("discoverOpenClawPlugins", () => {
601662}),
602663);
603664604-expect(candidates.find((candidate) => candidate.idHint === "feishu")).toEqual(
605-expect.objectContaining({ origin: "bundled" }),
606-);
665+expectCandidateFields(findCandidateById(candidates, "feishu"), { origin: "bundled" });
607666expect(countMatching(candidates, (candidate) => candidate.idHint === "feishu")).toBe(1);
608-expect(diagnostics).toEqual([
609- expect.objectContaining({
610- level: "warn",
611- source: bundledPluginDir,
612- message: expect.stringContaining("ignored plugins.load.paths entry"),
613-}),
614-]);
667+expect(diagnostics).toHaveLength(1);
668+expectDiagnostic({
669+diagnostics,
670+level: "warn",
671+source: bundledPluginDir,
672+messageIncludes: "ignored plugins.load.paths entry",
673+});
615674});
616675617676it("ignores legacy bundled plugin load paths that would shadow packaged bundled plugins", () => {
@@ -639,17 +698,15 @@ describe("discoverOpenClawPlugins", () => {
639698}),
640699);
641700642-expect(candidates.find((candidate) => candidate.idHint === "telegram")).toEqual(
643-expect.objectContaining({ origin: "bundled" }),
644-);
701+expectCandidateFields(findCandidateById(candidates, "telegram"), { origin: "bundled" });
645702expect(countMatching(candidates, (candidate) => candidate.idHint === "telegram")).toBe(1);
646-expect(diagnostics).toEqual([
647- expect.objectContaining({
648- level: "warn",
649- source: legacyPluginDir,
650- message: expect.stringContaining("legacy bundled plugin directory"),
651-}),
652-]);
703+expect(diagnostics).toHaveLength(1);
704+expectDiagnostic({
705+diagnostics,
706+level: "warn",
707+source: legacyPluginDir,
708+messageIncludes: "legacy bundled plugin directory",
709+});
653710});
654711655712it("discovers bind-mounted bundled source overlays before packaged dist bundles", () => {
@@ -686,25 +743,24 @@ describe("discoverOpenClawPlugins", () => {
686743const synologyCandidates = candidates.filter(
687744(candidate) => candidate.idHint === "synology-chat",
688745);
689-expect(synologyCandidates).toEqual([
690-expect.objectContaining({
691-origin: "bundled",
692-rootDir: fs.realpathSync(sourcePluginDir),
693-source: fs.realpathSync(sourceEntryPath),
694-}),
695-expect.objectContaining({
696-origin: "bundled",
697-rootDir: fs.realpathSync(bundledPluginDir),
698-source: fs.realpathSync(bundledEntryPath),
699-}),
700-]);
701-expect(diagnostics).toEqual([
702-expect.objectContaining({
703-level: "warn",
704-source: sourcePluginDir,
705-message: expect.stringContaining("bind-mounted bundled plugin source overlay"),
706-}),
707-]);
746+expect(synologyCandidates).toHaveLength(2);
747+expectCandidateFields(synologyCandidates[0], {
748+origin: "bundled",
749+rootDir: fs.realpathSync(sourcePluginDir),
750+source: fs.realpathSync(sourceEntryPath),
751+});
752+expectCandidateFields(synologyCandidates[1], {
753+origin: "bundled",
754+rootDir: fs.realpathSync(bundledPluginDir),
755+source: fs.realpathSync(bundledEntryPath),
756+});
757+expect(diagnostics).toHaveLength(1);
758+expectDiagnostic({
759+ diagnostics,
760+level: "warn",
761+source: sourcePluginDir,
762+messageIncludes: "bind-mounted bundled plugin source overlay",
763+});
708764});
709765710766it("keeps copied source plugin dirs inert when they are not mounted overlays", () => {
@@ -737,13 +793,11 @@ describe("discoverOpenClawPlugins", () => {
737793}),
738794);
739795740-expect(candidates.find((candidate) => candidate.idHint === "synology-chat")).toEqual(
741-expect.objectContaining({
742-origin: "bundled",
743-rootDir: fs.realpathSync(bundledPluginDir),
744-source: fs.realpathSync(bundledEntryPath),
745-}),
746-);
796+expectCandidateFields(findCandidateById(candidates, "synology-chat"), {
797+origin: "bundled",
798+rootDir: fs.realpathSync(bundledPluginDir),
799+source: fs.realpathSync(bundledEntryPath),
800+});
747801expect(countMatching(candidates, (candidate) => candidate.idHint === "synology-chat")).toBe(1);
748802expect(diagnostics).toStrictEqual([]);
749803});
@@ -829,12 +883,11 @@ describe("discoverOpenClawPlugins", () => {
829883const discordCandidates = result.candidates.filter(
830884(candidate) => candidate.idHint === "discord",
831885);
832-expect(discordCandidates).toEqual([
833-expect.objectContaining({
834-origin: "bundled",
835-source: fs.realpathSync(path.join(bundledPluginDir, "index.js")),
836-}),
837-]);
886+expect(discordCandidates).toHaveLength(1);
887+expectCandidateFields(discordCandidates[0], {
888+origin: "bundled",
889+source: fs.realpathSync(path.join(bundledPluginDir, "index.js")),
890+});
838891expect(
839892result.diagnostics.some(
840893(entry) =>
@@ -1684,9 +1737,10 @@ describe("discoverOpenClawPlugins", () => {
16841737const result = await discoverWithStateDir(stateDir, {});
1685173816861739expect(result.candidates).toHaveLength(0);
1687-expect(result.diagnostics.map((diag) => diag.message)).toEqual(
1688-expect.arrayContaining([expect.stringContaining("world-writable path")]),
1689-);
1740+expectDiagnostic({
1741+diagnostics: result.diagnostics,
1742+messageIncludes: "world-writable path",
1743+});
16901744});
1691174516921746it.runIf(process.platform !== "win32")(
@@ -1707,14 +1761,11 @@ describe("discoverOpenClawPlugins", () => {
17071761);
1708176217091763expect(result.candidates.map((candidate) => candidate.idHint)).toContain("demo-pack");
1710-expect(result.diagnostics).not.toEqual(
1711-expect.arrayContaining([
1712-expect.objectContaining({
1713-source: packDir,
1714-message: expect.stringContaining("world-writable path"),
1715-}),
1716-]),
1717-);
1764+expectNoDiagnostic({
1765+diagnostics: result.diagnostics,
1766+source: packDir,
1767+messageIncludes: "world-writable path",
1768+});
17181769expect(fs.statSync(packDir).mode & 0o777).toBe(0o755);
17191770},
17201771);
@@ -1735,17 +1786,16 @@ describe("discoverOpenClawPlugins", () => {
17351786const result = await discoverWithStateDir(stateDir, { ownershipUid: actualUid + 1 });
17361787const shouldBlockForMismatch = actualUid !== 0;
17371788expect(result.candidates).toHaveLength(shouldBlockForMismatch ? 0 : 1);
1738-expect(result.diagnostics.map((diag) => diag.message)).toEqual(
1739-shouldBlockForMismatch
1740- ? expect.arrayContaining([expect.stringContaining("suspicious ownership")])
1741- : expect.not.arrayContaining([expect.stringContaining("suspicious ownership")]),
1789+const hasSuspiciousOwnershipDiagnostic = result.diagnostics.some((diagnostic) =>
1790+diagnostic.message.includes("suspicious ownership"),
17421791);
1792+expect(hasSuspiciousOwnershipDiagnostic).toBe(shouldBlockForMismatch);
17431793if (shouldBlockForMismatch) {
1744-expect(result.diagnostics).toContainEqual(
1745-expect.objectContaining({
1746- pluginId: "owner-mismatch",
1747-}),
1748-);
1794+expectDiagnostic({
1795+diagnostics: result.diagnostics,
1796+pluginId: "owner-mismatch",
1797+messageIncludes: "suspicious ownership",
1798+});
17491799}
17501800},
17511801);
@@ -1795,13 +1845,13 @@ describe("discoverOpenClawPlugins", () => {
17951845},
17961846});
17971847expect(result.candidates).toHaveLength(0);
1798-expect(result.diagnostics).toContainEqual(
1799-expect.objectContaining({
1800-pluginId: "actual-id",
1801-source: expect.stringMatching(/alias-dir$/),
1802-message: expect.stringContaining("blocked plugin candidate: world-writable path"),
1803-}),
1848+const diagnostic = result.diagnostics.find(
1849+(entry) =>
1850+entry.pluginId === "actual-id" &&
1851+/alias-dir$/u.test(entry.source ?? "") &&
1852+entry.message.includes("blocked plugin candidate: world-writable path"),
18041853);
1854+expect(diagnostic).toBeDefined();
18051855} finally {
18061856fs.chmodSync(pluginDir, 0o755);
18071857}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。