mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 23:00:22 +00:00
test: dedupe loader and audit suites
This commit is contained in:
@@ -176,8 +176,8 @@ describe("resolveHeartbeatPrompt", () => {
|
||||
expected: "ping",
|
||||
},
|
||||
] as const;
|
||||
for (const testCase of cases) {
|
||||
expect(resolveHeartbeatPrompt(testCase.cfg)).toBe(testCase.expected);
|
||||
for (const { cfg, expected } of cases) {
|
||||
expect(resolveHeartbeatPrompt(cfg)).toBe(expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -346,11 +346,8 @@ describe("resolveHeartbeatDeliveryTarget", () => {
|
||||
},
|
||||
},
|
||||
];
|
||||
for (const testCase of cases) {
|
||||
expect(
|
||||
resolveHeartbeatDeliveryTarget({ cfg: testCase.cfg, entry: testCase.entry }),
|
||||
testCase.name,
|
||||
).toEqual(testCase.expected);
|
||||
for (const { cfg, entry, name, expected } of cases) {
|
||||
expect(resolveHeartbeatDeliveryTarget({ cfg, entry }), name).toEqual(expected);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -359,18 +356,18 @@ describe("resolveHeartbeatDeliveryTarget", () => {
|
||||
{ to: "-100111:topic:42", expectedTo: "-100111", expectedThreadId: 42 },
|
||||
{ to: "-100111", expectedTo: "-100111", expectedThreadId: undefined },
|
||||
] as const;
|
||||
for (const testCase of cases) {
|
||||
for (const { to, expectedTo, expectedThreadId } of cases) {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: { target: "telegram", to: testCase.to },
|
||||
heartbeat: { target: "telegram", to },
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = resolveHeartbeatDeliveryTarget({ cfg, entry: baseEntry });
|
||||
expect(result.channel).toBe("telegram");
|
||||
expect(result.to).toBe(testCase.expectedTo);
|
||||
expect(result.threadId).toBe(testCase.expectedThreadId);
|
||||
expect(result.to).toBe(expectedTo);
|
||||
expect(result.threadId).toBe(expectedThreadId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -398,16 +395,16 @@ describe("resolveHeartbeatDeliveryTarget", () => {
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
for (const { accountId, expected } of cases) {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: { target: "telegram", to: "-100123", accountId: testCase.accountId },
|
||||
heartbeat: { target: "telegram", to: "-100123", accountId },
|
||||
},
|
||||
},
|
||||
channels: { telegram: { accounts: { work: { botToken: "token" } } } },
|
||||
};
|
||||
expect(resolveHeartbeatDeliveryTarget({ cfg, entry: baseEntry })).toEqual(testCase.expected);
|
||||
expect(resolveHeartbeatDeliveryTarget({ cfg, entry: baseEntry })).toEqual(expected);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -779,8 +776,8 @@ describe("runHeartbeatOnce", () => {
|
||||
},
|
||||
]);
|
||||
|
||||
for (const testCase of cases) {
|
||||
const tmpDir = await createCaseDir(testCase.caseDir);
|
||||
for (const { name, caseDir, peerKind, peerId, message, applyOverride, runOptions } of cases) {
|
||||
const tmpDir = await createCaseDir(caseDir);
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: {
|
||||
@@ -800,10 +797,10 @@ describe("runHeartbeatOnce", () => {
|
||||
const overrideSessionKey = buildAgentPeerSessionKey({
|
||||
agentId,
|
||||
channel: "whatsapp",
|
||||
peerKind: testCase.peerKind,
|
||||
peerId: testCase.peerId,
|
||||
peerKind,
|
||||
peerId,
|
||||
});
|
||||
testCase.applyOverride({ cfg, sessionKey: overrideSessionKey });
|
||||
applyOverride({ cfg, sessionKey: overrideSessionKey });
|
||||
|
||||
await fs.writeFile(
|
||||
storePath,
|
||||
@@ -815,16 +812,16 @@ describe("runHeartbeatOnce", () => {
|
||||
lastTo: "120363401234567890@g.us",
|
||||
},
|
||||
[overrideSessionKey]: {
|
||||
sessionId: `sid-${testCase.peerKind}`,
|
||||
sessionId: `sid-${peerKind}`,
|
||||
updatedAt: Date.now() + 10_000,
|
||||
lastChannel: "whatsapp",
|
||||
lastTo: testCase.peerId,
|
||||
lastTo: peerId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
replySpy.mockClear();
|
||||
replySpy.mockResolvedValue([{ text: testCase.message }]);
|
||||
replySpy.mockResolvedValue([{ text: message }]);
|
||||
const sendWhatsApp = vi
|
||||
.fn<
|
||||
(
|
||||
@@ -837,21 +834,17 @@ describe("runHeartbeatOnce", () => {
|
||||
|
||||
await runHeartbeatOnce({
|
||||
cfg,
|
||||
...testCase.runOptions({ sessionKey: overrideSessionKey }),
|
||||
...runOptions({ sessionKey: overrideSessionKey }),
|
||||
deps: createHeartbeatDeps(sendWhatsApp),
|
||||
});
|
||||
|
||||
expect(sendWhatsApp, testCase.name).toHaveBeenCalledTimes(1);
|
||||
expect(sendWhatsApp, testCase.name).toHaveBeenCalledWith(
|
||||
testCase.peerId,
|
||||
testCase.message,
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(replySpy, testCase.name).toHaveBeenCalledWith(
|
||||
expect(sendWhatsApp, name).toHaveBeenCalledTimes(1);
|
||||
expect(sendWhatsApp, name).toHaveBeenCalledWith(peerId, message, expect.any(Object));
|
||||
expect(replySpy, name).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
SessionKey: overrideSessionKey,
|
||||
From: testCase.peerId,
|
||||
To: testCase.peerId,
|
||||
From: peerId,
|
||||
To: peerId,
|
||||
Provider: "heartbeat",
|
||||
}),
|
||||
expect.objectContaining({ isHeartbeat: true, suppressToolErrorWarnings: false }),
|
||||
@@ -939,8 +932,8 @@ describe("runHeartbeatOnce", () => {
|
||||
},
|
||||
]);
|
||||
|
||||
for (const testCase of cases) {
|
||||
const tmpDir = await createCaseDir(testCase.caseDir);
|
||||
for (const { name, caseDir, replies, expectedTexts } of cases) {
|
||||
const tmpDir = await createCaseDir(caseDir);
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: {
|
||||
@@ -972,7 +965,7 @@ describe("runHeartbeatOnce", () => {
|
||||
);
|
||||
|
||||
replySpy.mockClear();
|
||||
replySpy.mockResolvedValue(testCase.replies);
|
||||
replySpy.mockResolvedValue(replies);
|
||||
const sendWhatsApp = vi
|
||||
.fn<
|
||||
(
|
||||
@@ -988,9 +981,9 @@ describe("runHeartbeatOnce", () => {
|
||||
deps: createHeartbeatDeps(sendWhatsApp),
|
||||
});
|
||||
|
||||
expect(sendWhatsApp, testCase.name).toHaveBeenCalledTimes(testCase.expectedTexts.length);
|
||||
for (const [index, text] of testCase.expectedTexts.entries()) {
|
||||
expect(sendWhatsApp, testCase.name).toHaveBeenNthCalledWith(
|
||||
expect(sendWhatsApp, name).toHaveBeenCalledTimes(expectedTexts.length);
|
||||
for (const [index, text] of expectedTexts.entries()) {
|
||||
expect(sendWhatsApp, name).toHaveBeenNthCalledWith(
|
||||
index + 1,
|
||||
"120363401234567890@g.us",
|
||||
text,
|
||||
@@ -1241,19 +1234,27 @@ describe("runHeartbeatOnce", () => {
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of cases) {
|
||||
const { res, replySpy, sendWhatsApp } = await runHeartbeatFileScenario(testCase);
|
||||
for (const {
|
||||
name,
|
||||
expectedStatus,
|
||||
expectedSkipReason,
|
||||
expectedReplyCalls,
|
||||
expectedSendCalls,
|
||||
expectCronContext,
|
||||
...scenario
|
||||
} of cases) {
|
||||
const { res, replySpy, sendWhatsApp } = await runHeartbeatFileScenario(scenario);
|
||||
try {
|
||||
expect(res.status, testCase.name).toBe(testCase.expectedStatus);
|
||||
expect(res.status, name).toBe(expectedStatus);
|
||||
if (res.status === "skipped") {
|
||||
expect(res.reason, testCase.name).toBe(testCase.expectedSkipReason);
|
||||
expect(res.reason, name).toBe(expectedSkipReason);
|
||||
}
|
||||
expect(replySpy, testCase.name).toHaveBeenCalledTimes(testCase.expectedReplyCalls);
|
||||
expect(sendWhatsApp, testCase.name).toHaveBeenCalledTimes(testCase.expectedSendCalls);
|
||||
if (testCase.expectCronContext) {
|
||||
expect(replySpy, name).toHaveBeenCalledTimes(expectedReplyCalls);
|
||||
expect(sendWhatsApp, name).toHaveBeenCalledTimes(expectedSendCalls);
|
||||
if (expectCronContext) {
|
||||
const calledCtx = replySpy.mock.calls[0]?.[0] as { Provider?: string; Body?: string };
|
||||
expect(calledCtx.Provider, testCase.name).toBe("cron-event");
|
||||
expect(calledCtx.Body, testCase.name).toContain("scheduled reminder has been triggered");
|
||||
expect(calledCtx.Provider, name).toBe("cron-event");
|
||||
expect(calledCtx.Body, name).toContain("scheduled reminder has been triggered");
|
||||
}
|
||||
} finally {
|
||||
replySpy.mockRestore();
|
||||
|
||||
@@ -748,70 +748,69 @@ describe("loadOpenClawPlugins", () => {
|
||||
expect(bundled?.status).toBe("disabled");
|
||||
});
|
||||
|
||||
it("handles bundled telegram plugin enablement and override rules", () => {
|
||||
setupBundledTelegramPlugin();
|
||||
const cases = [
|
||||
{
|
||||
name: "loads bundled telegram plugin when enabled",
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["telegram"],
|
||||
entries: {
|
||||
telegram: { enabled: true },
|
||||
},
|
||||
it.each([
|
||||
{
|
||||
name: "loads bundled telegram plugin when enabled",
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["telegram"],
|
||||
entries: {
|
||||
telegram: { enabled: true },
|
||||
},
|
||||
} satisfies PluginLoadConfig,
|
||||
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
|
||||
expectTelegramLoaded(registry);
|
||||
},
|
||||
} satisfies PluginLoadConfig,
|
||||
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
|
||||
expectTelegramLoaded(registry);
|
||||
},
|
||||
{
|
||||
name: "loads bundled channel plugins when channels.<id>.enabled=true",
|
||||
config: {
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
},
|
||||
{
|
||||
name: "loads bundled channel plugins when channels.<id>.enabled=true",
|
||||
config: {
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
},
|
||||
} satisfies PluginLoadConfig,
|
||||
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
|
||||
expectTelegramLoaded(registry);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "still respects explicit disable via plugins.entries for bundled channels",
|
||||
config: {
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
telegram: { enabled: false },
|
||||
},
|
||||
},
|
||||
} satisfies PluginLoadConfig,
|
||||
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
|
||||
const telegram = registry.plugins.find((entry) => entry.id === "telegram");
|
||||
expect(telegram?.status).toBe("disabled");
|
||||
expect(telegram?.error).toBe("disabled in config");
|
||||
plugins: {
|
||||
enabled: true,
|
||||
},
|
||||
} satisfies PluginLoadConfig,
|
||||
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
|
||||
expectTelegramLoaded(registry);
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
},
|
||||
{
|
||||
name: "still respects explicit disable via plugins.entries for bundled channels",
|
||||
config: {
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
telegram: { enabled: false },
|
||||
},
|
||||
},
|
||||
} satisfies PluginLoadConfig,
|
||||
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
|
||||
const telegram = registry.plugins.find((entry) => entry.id === "telegram");
|
||||
expect(telegram?.status).toBe("disabled");
|
||||
expect(telegram?.error).toBe("disabled in config");
|
||||
},
|
||||
},
|
||||
] as const)(
|
||||
"handles bundled telegram plugin enablement and override rules: $name",
|
||||
({ config, assert }) => {
|
||||
setupBundledTelegramPlugin();
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
workspaceDir: cachedBundledTelegramDir,
|
||||
config: testCase.config,
|
||||
config,
|
||||
});
|
||||
testCase.assert(registry);
|
||||
}
|
||||
});
|
||||
assert(registry);
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves package.json metadata for bundled memory plugins", () => {
|
||||
const registry = loadBundledMemoryPluginRegistry({
|
||||
@@ -830,150 +829,146 @@ describe("loadOpenClawPlugins", () => {
|
||||
expect(memory?.name).toBe("Memory (Core)");
|
||||
expect(memory?.version).toBe("1.2.3");
|
||||
});
|
||||
it("handles config-path and scoped plugin loads", () => {
|
||||
const scenarios = [
|
||||
{
|
||||
label: "loads plugins from config paths",
|
||||
run: () => {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
|
||||
const plugin = writePlugin({
|
||||
id: "allowed-config-path",
|
||||
filename: "allowed-config-path.cjs",
|
||||
body: `module.exports = {
|
||||
it.each([
|
||||
{
|
||||
label: "loads plugins from config paths",
|
||||
run: () => {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
|
||||
const plugin = writePlugin({
|
||||
id: "allowed-config-path",
|
||||
filename: "allowed-config-path.cjs",
|
||||
body: `module.exports = {
|
||||
id: "allowed-config-path",
|
||||
register(api) {
|
||||
api.registerGatewayMethod("allowed-config-path.ping", ({ respond }) => respond(true, { ok: true }));
|
||||
},
|
||||
};`,
|
||||
});
|
||||
});
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
workspaceDir: plugin.dir,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [plugin.file] },
|
||||
allow: ["allowed-config-path"],
|
||||
},
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
workspaceDir: plugin.dir,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [plugin.file] },
|
||||
allow: ["allowed-config-path"],
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const loaded = registry.plugins.find((entry) => entry.id === "allowed-config-path");
|
||||
expect(loaded?.status).toBe("loaded");
|
||||
expect(Object.keys(registry.gatewayHandlers)).toContain("allowed-config-path.ping");
|
||||
},
|
||||
const loaded = registry.plugins.find((entry) => entry.id === "allowed-config-path");
|
||||
expect(loaded?.status).toBe("loaded");
|
||||
expect(Object.keys(registry.gatewayHandlers)).toContain("allowed-config-path.ping");
|
||||
},
|
||||
{
|
||||
label: "limits imports to the requested plugin ids",
|
||||
run: () => {
|
||||
useNoBundledPlugins();
|
||||
const allowed = writePlugin({
|
||||
id: "allowed-scoped-only",
|
||||
filename: "allowed-scoped-only.cjs",
|
||||
body: `module.exports = { id: "allowed-scoped-only", register() {} };`,
|
||||
});
|
||||
const skippedMarker = path.join(makeTempDir(), "skipped-loaded.txt");
|
||||
const skipped = writePlugin({
|
||||
id: "skipped-scoped-only",
|
||||
filename: "skipped-scoped-only.cjs",
|
||||
body: `require("node:fs").writeFileSync(${JSON.stringify(skippedMarker)}, "loaded", "utf-8");
|
||||
},
|
||||
{
|
||||
label: "limits imports to the requested plugin ids",
|
||||
run: () => {
|
||||
useNoBundledPlugins();
|
||||
const allowed = writePlugin({
|
||||
id: "allowed-scoped-only",
|
||||
filename: "allowed-scoped-only.cjs",
|
||||
body: `module.exports = { id: "allowed-scoped-only", register() {} };`,
|
||||
});
|
||||
const skippedMarker = path.join(makeTempDir(), "skipped-loaded.txt");
|
||||
const skipped = writePlugin({
|
||||
id: "skipped-scoped-only",
|
||||
filename: "skipped-scoped-only.cjs",
|
||||
body: `require("node:fs").writeFileSync(${JSON.stringify(skippedMarker)}, "loaded", "utf-8");
|
||||
module.exports = { id: "skipped-scoped-only", register() { throw new Error("skipped plugin should not load"); } };`,
|
||||
});
|
||||
});
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [allowed.file, skipped.file] },
|
||||
allow: ["allowed-scoped-only", "skipped-scoped-only"],
|
||||
},
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [allowed.file, skipped.file] },
|
||||
allow: ["allowed-scoped-only", "skipped-scoped-only"],
|
||||
},
|
||||
onlyPluginIds: ["allowed-scoped-only"],
|
||||
});
|
||||
},
|
||||
onlyPluginIds: ["allowed-scoped-only"],
|
||||
});
|
||||
|
||||
expect(registry.plugins.map((entry) => entry.id)).toEqual(["allowed-scoped-only"]);
|
||||
expect(fs.existsSync(skippedMarker)).toBe(false);
|
||||
},
|
||||
expect(registry.plugins.map((entry) => entry.id)).toEqual(["allowed-scoped-only"]);
|
||||
expect(fs.existsSync(skippedMarker)).toBe(false);
|
||||
},
|
||||
{
|
||||
label: "keeps scoped plugin loads in a separate cache entry",
|
||||
run: () => {
|
||||
useNoBundledPlugins();
|
||||
const allowed = writePlugin({
|
||||
id: "allowed-cache-scope",
|
||||
filename: "allowed-cache-scope.cjs",
|
||||
body: `module.exports = { id: "allowed-cache-scope", register() {} };`,
|
||||
});
|
||||
const extra = writePlugin({
|
||||
id: "extra-cache-scope",
|
||||
filename: "extra-cache-scope.cjs",
|
||||
body: `module.exports = { id: "extra-cache-scope", register() {} };`,
|
||||
});
|
||||
const options = {
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [allowed.file, extra.file] },
|
||||
allow: ["allowed-cache-scope", "extra-cache-scope"],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "keeps scoped plugin loads in a separate cache entry",
|
||||
run: () => {
|
||||
useNoBundledPlugins();
|
||||
const allowed = writePlugin({
|
||||
id: "allowed-cache-scope",
|
||||
filename: "allowed-cache-scope.cjs",
|
||||
body: `module.exports = { id: "allowed-cache-scope", register() {} };`,
|
||||
});
|
||||
const extra = writePlugin({
|
||||
id: "extra-cache-scope",
|
||||
filename: "extra-cache-scope.cjs",
|
||||
body: `module.exports = { id: "extra-cache-scope", register() {} };`,
|
||||
});
|
||||
const options = {
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [allowed.file, extra.file] },
|
||||
allow: ["allowed-cache-scope", "extra-cache-scope"],
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const full = loadOpenClawPlugins(options);
|
||||
const scoped = loadOpenClawPlugins({
|
||||
...options,
|
||||
onlyPluginIds: ["allowed-cache-scope"],
|
||||
});
|
||||
const scopedAgain = loadOpenClawPlugins({
|
||||
...options,
|
||||
onlyPluginIds: ["allowed-cache-scope"],
|
||||
});
|
||||
const full = loadOpenClawPlugins(options);
|
||||
const scoped = loadOpenClawPlugins({
|
||||
...options,
|
||||
onlyPluginIds: ["allowed-cache-scope"],
|
||||
});
|
||||
const scopedAgain = loadOpenClawPlugins({
|
||||
...options,
|
||||
onlyPluginIds: ["allowed-cache-scope"],
|
||||
});
|
||||
|
||||
expect(full.plugins.map((entry) => entry.id).toSorted()).toEqual([
|
||||
"allowed-cache-scope",
|
||||
"extra-cache-scope",
|
||||
]);
|
||||
expect(scoped).not.toBe(full);
|
||||
expect(scoped.plugins.map((entry) => entry.id)).toEqual(["allowed-cache-scope"]);
|
||||
expect(scopedAgain).toBe(scoped);
|
||||
},
|
||||
expect(full.plugins.map((entry) => entry.id).toSorted()).toEqual([
|
||||
"allowed-cache-scope",
|
||||
"extra-cache-scope",
|
||||
]);
|
||||
expect(scoped).not.toBe(full);
|
||||
expect(scoped.plugins.map((entry) => entry.id)).toEqual(["allowed-cache-scope"]);
|
||||
expect(scopedAgain).toBe(scoped);
|
||||
},
|
||||
{
|
||||
label: "can load a scoped registry without replacing the active global registry",
|
||||
run: () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
id: "allowed-nonactivating-scope",
|
||||
filename: "allowed-nonactivating-scope.cjs",
|
||||
body: `module.exports = { id: "allowed-nonactivating-scope", register() {} };`,
|
||||
});
|
||||
const previousRegistry = createEmptyPluginRegistry();
|
||||
setActivePluginRegistry(previousRegistry, "existing-registry");
|
||||
resetGlobalHookRunner();
|
||||
},
|
||||
{
|
||||
label: "can load a scoped registry without replacing the active global registry",
|
||||
run: () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
id: "allowed-nonactivating-scope",
|
||||
filename: "allowed-nonactivating-scope.cjs",
|
||||
body: `module.exports = { id: "allowed-nonactivating-scope", register() {} };`,
|
||||
});
|
||||
const previousRegistry = createEmptyPluginRegistry();
|
||||
setActivePluginRegistry(previousRegistry, "existing-registry");
|
||||
resetGlobalHookRunner();
|
||||
|
||||
const scoped = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
activate: false,
|
||||
workspaceDir: plugin.dir,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [plugin.file] },
|
||||
allow: ["allowed-nonactivating-scope"],
|
||||
},
|
||||
const scoped = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
activate: false,
|
||||
workspaceDir: plugin.dir,
|
||||
config: {
|
||||
plugins: {
|
||||
load: { paths: [plugin.file] },
|
||||
allow: ["allowed-nonactivating-scope"],
|
||||
},
|
||||
onlyPluginIds: ["allowed-nonactivating-scope"],
|
||||
});
|
||||
},
|
||||
onlyPluginIds: ["allowed-nonactivating-scope"],
|
||||
});
|
||||
|
||||
expect(scoped.plugins.map((entry) => entry.id)).toEqual(["allowed-nonactivating-scope"]);
|
||||
expect(getActivePluginRegistry()).toBe(previousRegistry);
|
||||
expect(getActivePluginRegistryKey()).toBe("existing-registry");
|
||||
expect(getGlobalHookRunner()).toBeNull();
|
||||
},
|
||||
expect(scoped.plugins.map((entry) => entry.id)).toEqual(["allowed-nonactivating-scope"]);
|
||||
expect(getActivePluginRegistry()).toBe(previousRegistry);
|
||||
expect(getActivePluginRegistryKey()).toBe("existing-registry");
|
||||
expect(getGlobalHookRunner()).toBeNull();
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const scenario of scenarios) {
|
||||
scenario.run();
|
||||
}
|
||||
},
|
||||
] as const)("handles config-path and scoped plugin loads: $label", ({ run }) => {
|
||||
run();
|
||||
});
|
||||
|
||||
it("only publishes plugin commands to the global registry during activating loads", async () => {
|
||||
@@ -2674,9 +2669,9 @@ module.exports = {
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const scenario of scenarios) {
|
||||
const registry = scenario.loadRegistry();
|
||||
scenario.assert(registry);
|
||||
for (const { loadRegistry, assert } of scenarios) {
|
||||
const registry = loadRegistry();
|
||||
assert(registry);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -553,10 +553,12 @@ description: test skill
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
const res = await testCase.run();
|
||||
testCase.assert(res);
|
||||
}
|
||||
await Promise.all(
|
||||
cases.map(async (testCase) => {
|
||||
const res = await testCase.run();
|
||||
testCase.assert(res);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("scores dangerous gateway.tools.allow over HTTP by exposure", async () => {
|
||||
@@ -2230,23 +2232,25 @@ description: test skill
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
await withChannelSecurityStateDir(async () => {
|
||||
const res = await runSecurityAudit({
|
||||
config: testCase.cfg,
|
||||
includeFilesystem: false,
|
||||
includeChannelSecurity: true,
|
||||
plugins: [discordPlugin],
|
||||
});
|
||||
await Promise.all(
|
||||
cases.map(async (testCase) => {
|
||||
await withChannelSecurityStateDir(async () => {
|
||||
const res = await runSecurityAudit({
|
||||
config: testCase.cfg,
|
||||
includeFilesystem: false,
|
||||
includeChannelSecurity: true,
|
||||
plugins: [discordPlugin],
|
||||
});
|
||||
|
||||
expect(
|
||||
res.findings.some(
|
||||
(finding) => finding.checkId === "channels.discord.commands.native.no_allowlists",
|
||||
),
|
||||
testCase.name,
|
||||
).toBe(testCase.expectFinding);
|
||||
});
|
||||
}
|
||||
expect(
|
||||
res.findings.some(
|
||||
(finding) => finding.checkId === "channels.discord.commands.native.no_allowlists",
|
||||
),
|
||||
testCase.name,
|
||||
).toBe(testCase.expectFinding);
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps source-configured channel security findings when resolved inspection is incomplete", async () => {
|
||||
@@ -2440,26 +2444,28 @@ description: test skill
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
await withChannelSecurityStateDir(async () => {
|
||||
const res = await runSecurityAudit({
|
||||
config: testCase.resolvedConfig,
|
||||
sourceConfig: testCase.sourceConfig,
|
||||
includeFilesystem: false,
|
||||
includeChannelSecurity: true,
|
||||
plugins: [testCase.plugin(testCase.sourceConfig)],
|
||||
});
|
||||
await Promise.all(
|
||||
cases.map(async (testCase) => {
|
||||
await withChannelSecurityStateDir(async () => {
|
||||
const res = await runSecurityAudit({
|
||||
config: testCase.resolvedConfig,
|
||||
sourceConfig: testCase.sourceConfig,
|
||||
includeFilesystem: false,
|
||||
includeChannelSecurity: true,
|
||||
plugins: [testCase.plugin(testCase.sourceConfig)],
|
||||
});
|
||||
|
||||
expect(res.findings, testCase.name).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
checkId: testCase.expectedCheckId,
|
||||
severity: "warn",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
}
|
||||
expect(res.findings, testCase.name).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
checkId: testCase.expectedCheckId,
|
||||
severity: "warn",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("adds a read-only resolution warning when channel account resolveAccount throws", async () => {
|
||||
|
||||
Reference in New Issue
Block a user