mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-31 20:01:36 +00:00
test: dedupe plugin utility config suites
This commit is contained in:
@@ -37,6 +37,22 @@ function expectGeneratedPathResolution(tempRoot: string, expectedRelativePath: s
|
||||
).toBe(path.join(tempRoot, expectedRelativePath));
|
||||
}
|
||||
|
||||
function expectArtifactPresence(
|
||||
artifacts: readonly string[] | undefined,
|
||||
params: { contains?: readonly string[]; excludes?: readonly string[] },
|
||||
) {
|
||||
if (params.contains) {
|
||||
for (const artifact of params.contains) {
|
||||
expect(artifacts).toContain(artifact);
|
||||
}
|
||||
}
|
||||
if (params.excludes) {
|
||||
for (const artifact of params.excludes) {
|
||||
expect(artifacts).not.toContain(artifact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function writeGeneratedMetadataModule(params: {
|
||||
repoRoot: string;
|
||||
outputPath?: string;
|
||||
@@ -64,11 +80,13 @@ describe("bundled plugin metadata", () => {
|
||||
const discord = BUNDLED_PLUGIN_METADATA.find((entry) => entry.dirName === "discord");
|
||||
expect(discord?.source).toEqual({ source: "./index.ts", built: "index.js" });
|
||||
expect(discord?.setupSource).toEqual({ source: "./setup-entry.ts", built: "setup-entry.js" });
|
||||
expect(discord?.publicSurfaceArtifacts).toContain("api.js");
|
||||
expect(discord?.publicSurfaceArtifacts).toContain("runtime-api.js");
|
||||
expect(discord?.publicSurfaceArtifacts).toContain("session-key-api.js");
|
||||
expect(discord?.publicSurfaceArtifacts).not.toContain("test-api.js");
|
||||
expect(discord?.runtimeSidecarArtifacts).toContain("runtime-api.js");
|
||||
expectArtifactPresence(discord?.publicSurfaceArtifacts, {
|
||||
contains: ["api.js", "runtime-api.js", "session-key-api.js"],
|
||||
excludes: ["test-api.js"],
|
||||
});
|
||||
expectArtifactPresence(discord?.runtimeSidecarArtifacts, {
|
||||
contains: ["runtime-api.js"],
|
||||
});
|
||||
expect(discord?.manifest.id).toBe("discord");
|
||||
expect(discord?.manifest.channelConfigs?.discord).toEqual(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -72,6 +72,10 @@ function expectStartupPluginIds(config: OpenClawConfig, expected: readonly strin
|
||||
).toEqual(expected);
|
||||
}
|
||||
|
||||
function expectManifestRegistryFixture() {
|
||||
expect(loadPluginManifestRegistry).toHaveBeenCalled();
|
||||
}
|
||||
|
||||
describe("resolveGatewayStartupPluginIds", () => {
|
||||
beforeEach(() => {
|
||||
listPotentialConfiguredChannelIds.mockReset().mockReturnValue(["demo-channel"]);
|
||||
@@ -119,5 +123,6 @@ describe("resolveGatewayStartupPluginIds", () => {
|
||||
],
|
||||
] as const)("%s", (_name, config, expected) => {
|
||||
expectStartupPluginIds(config, expected);
|
||||
expectManifestRegistryFixture();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,6 +46,14 @@ function createCliRegistry() {
|
||||
};
|
||||
}
|
||||
|
||||
function expectPluginLoaderConfig(config: OpenClawConfig) {
|
||||
expect(mocks.loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe("registerPluginCliCommands", () => {
|
||||
beforeEach(() => {
|
||||
mocks.memoryRegister.mockClear();
|
||||
@@ -101,11 +109,7 @@ describe("registerPluginCliCommands", () => {
|
||||
config: rawConfig,
|
||||
env: process.env,
|
||||
});
|
||||
expect(mocks.loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: autoEnabledConfig,
|
||||
}),
|
||||
);
|
||||
expectPluginLoaderConfig(autoEnabledConfig);
|
||||
expect(mocks.memoryRegister).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: autoEnabledConfig,
|
||||
|
||||
@@ -20,6 +20,15 @@ function expectResolvedEnableState(
|
||||
expect(resolveEnableState(...params)).toEqual(expected);
|
||||
}
|
||||
|
||||
function expectMemoryPluginState(
|
||||
config: Parameters<typeof normalizePluginsConfig>[0],
|
||||
expected: ReturnType<typeof resolveEnableState>,
|
||||
) {
|
||||
expect(resolveEnableState("memory-core", "bundled", normalizePluginsConfig(config))).toEqual(
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
describe("normalizePluginsConfig", () => {
|
||||
it.each([
|
||||
[{}, "memory-core"],
|
||||
@@ -177,22 +186,18 @@ describe("resolveEnableState", () => {
|
||||
);
|
||||
|
||||
it("keeps the selected memory slot plugin enabled even when omitted from plugins.allow", () => {
|
||||
const state = resolveEnableState(
|
||||
"memory-core",
|
||||
"bundled",
|
||||
normalizePluginsConfig({
|
||||
expectMemoryPluginState(
|
||||
{
|
||||
allow: ["telegram"],
|
||||
slots: { memory: "memory-core" },
|
||||
}),
|
||||
},
|
||||
{ enabled: true },
|
||||
);
|
||||
expect(state).toEqual({ enabled: true });
|
||||
});
|
||||
|
||||
it("keeps explicit disable authoritative for the selected memory slot plugin", () => {
|
||||
const state = resolveEnableState(
|
||||
"memory-core",
|
||||
"bundled",
|
||||
normalizePluginsConfig({
|
||||
expectMemoryPluginState(
|
||||
{
|
||||
allow: ["telegram"],
|
||||
slots: { memory: "memory-core" },
|
||||
entries: {
|
||||
@@ -200,9 +205,9 @@ describe("resolveEnableState", () => {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{ enabled: false, reason: "disabled in config" },
|
||||
);
|
||||
expect(state).toEqual({ enabled: false, reason: "disabled in config" });
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -15,6 +15,13 @@ function expectEnableResult(
|
||||
params.assert(result);
|
||||
}
|
||||
|
||||
function expectEnabledAllowlist(
|
||||
result: ReturnType<typeof enablePluginInConfig>,
|
||||
expected: string[],
|
||||
) {
|
||||
expect(result.config.plugins?.allow).toEqual(expected);
|
||||
}
|
||||
|
||||
describe("enablePluginInConfig", () => {
|
||||
it.each([
|
||||
{
|
||||
@@ -36,7 +43,7 @@ describe("enablePluginInConfig", () => {
|
||||
pluginId: "google",
|
||||
expectedEnabled: true,
|
||||
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
|
||||
expect(result.config.plugins?.allow).toEqual(["memory-core", "google"]);
|
||||
expectEnabledAllowlist(result, ["memory-core", "google"]);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -73,7 +80,7 @@ describe("enablePluginInConfig", () => {
|
||||
expectedEnabled: true,
|
||||
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
|
||||
expect(result.config.channels?.telegram?.enabled).toBe(true);
|
||||
expect(result.config.plugins?.allow).toEqual(["memory-core", "telegram"]);
|
||||
expectEnabledAllowlist(result, ["memory-core", "telegram"]);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,6 +9,20 @@ function expectRecordedInstall(pluginId: string, next: ReturnType<typeof recordP
|
||||
expect(typeof next.plugins?.installs?.[pluginId]?.installedAt).toBe("string");
|
||||
}
|
||||
|
||||
function createExpectedResolutionFields(
|
||||
overrides: Partial<ReturnType<typeof buildNpmResolutionInstallFields>>,
|
||||
) {
|
||||
return {
|
||||
resolvedName: undefined,
|
||||
resolvedVersion: undefined,
|
||||
resolvedSpec: undefined,
|
||||
integrity: undefined,
|
||||
shasum: undefined,
|
||||
resolvedAt: undefined,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("buildNpmResolutionInstallFields", () => {
|
||||
it.each([
|
||||
{
|
||||
@@ -21,26 +35,19 @@ describe("buildNpmResolutionInstallFields", () => {
|
||||
shasum: "deadbeef",
|
||||
resolvedAt: "2026-02-22T00:00:00.000Z",
|
||||
},
|
||||
expected: {
|
||||
expected: createExpectedResolutionFields({
|
||||
resolvedName: "@openclaw/demo",
|
||||
resolvedVersion: "1.2.3",
|
||||
resolvedSpec: "@openclaw/demo@1.2.3",
|
||||
integrity: "sha512-abc",
|
||||
shasum: "deadbeef",
|
||||
resolvedAt: "2026-02-22T00:00:00.000Z",
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "returns undefined fields when resolution is missing",
|
||||
input: undefined,
|
||||
expected: {
|
||||
resolvedName: undefined,
|
||||
resolvedVersion: undefined,
|
||||
resolvedSpec: undefined,
|
||||
integrity: undefined,
|
||||
shasum: undefined,
|
||||
resolvedAt: undefined,
|
||||
},
|
||||
expected: createExpectedResolutionFields({}),
|
||||
},
|
||||
] as const)("$name", ({ input, expected }) => {
|
||||
expect(buildNpmResolutionInstallFields(input)).toEqual(expected);
|
||||
|
||||
@@ -18,6 +18,22 @@ function createLazyModuleLifecycle() {
|
||||
};
|
||||
}
|
||||
|
||||
async function expectLifecycleStarted(params: {
|
||||
overrideEnvVar?: string;
|
||||
loadDefaultModule?: () => Promise<Record<string, unknown>>;
|
||||
loadOverrideModule?: (spec: string) => Promise<Record<string, unknown>>;
|
||||
startExportNames: string[];
|
||||
stopExportNames?: string[];
|
||||
}) {
|
||||
return startLazyPluginServiceModule({
|
||||
...(params.overrideEnvVar ? { overrideEnvVar: params.overrideEnvVar } : {}),
|
||||
loadDefaultModule: params.loadDefaultModule ?? (async () => createLazyModuleLifecycle().module),
|
||||
...(params.loadOverrideModule ? { loadOverrideModule: params.loadOverrideModule } : {}),
|
||||
startExportNames: params.startExportNames,
|
||||
...(params.stopExportNames ? { stopExportNames: params.stopExportNames } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
describe("startLazyPluginServiceModule", () => {
|
||||
afterEach(() => {
|
||||
delete process.env.OPENCLAW_LAZY_SERVICE_SKIP;
|
||||
@@ -27,7 +43,7 @@ describe("startLazyPluginServiceModule", () => {
|
||||
it("starts the default module and returns its stop hook", async () => {
|
||||
const lifecycle = createLazyModuleLifecycle();
|
||||
|
||||
const handle = await startLazyPluginServiceModule({
|
||||
const handle = await expectLifecycleStarted({
|
||||
loadDefaultModule: async () => lifecycle.module,
|
||||
startExportNames: ["startDefault"],
|
||||
stopExportNames: ["stopDefault"],
|
||||
@@ -58,7 +74,7 @@ describe("startLazyPluginServiceModule", () => {
|
||||
const start = createAsyncHookMock();
|
||||
const loadOverrideModule = vi.fn(async () => ({ startOverride: start }));
|
||||
|
||||
await startLazyPluginServiceModule({
|
||||
await expectLifecycleStarted({
|
||||
overrideEnvVar: "OPENCLAW_LAZY_SERVICE_OVERRIDE",
|
||||
loadDefaultModule: async () => ({ startDefault: createAsyncHookMock() }),
|
||||
loadOverrideModule,
|
||||
|
||||
@@ -39,6 +39,10 @@ function expectServiceContext(
|
||||
expect(ctx.config).toBe(config);
|
||||
expect(ctx.workspaceDir).toBe("/tmp/workspace");
|
||||
expect(ctx.stateDir).toBe(STATE_DIR);
|
||||
expectServiceLogger(ctx);
|
||||
}
|
||||
|
||||
function expectServiceLogger(ctx: OpenClawPluginServiceContext) {
|
||||
expect(ctx.logger).toBeDefined();
|
||||
expect(typeof ctx.logger.info).toBe("function");
|
||||
expect(typeof ctx.logger.warn).toBe("function");
|
||||
|
||||
@@ -63,6 +63,11 @@ describe("applyExclusiveSlotSelection", () => {
|
||||
}
|
||||
}
|
||||
|
||||
function expectUnchangedSelection(result: ReturnType<typeof applyExclusiveSlotSelection>) {
|
||||
expect(result.changed).toBe(false);
|
||||
expect(result.warnings).toHaveLength(0);
|
||||
}
|
||||
|
||||
it("selects the slot and disables other entries for the same kind", () => {
|
||||
const config = createMemoryConfig({
|
||||
slots: { memory: "memory-core" },
|
||||
@@ -94,8 +99,7 @@ describe("applyExclusiveSlotSelection", () => {
|
||||
registry: { plugins: [{ id: "memory", kind: "memory" }] },
|
||||
});
|
||||
|
||||
expect(result.changed).toBe(false);
|
||||
expect(result.warnings).toHaveLength(0);
|
||||
expectUnchangedSelection(result);
|
||||
expect(result.config).toBe(config);
|
||||
});
|
||||
|
||||
@@ -140,8 +144,7 @@ describe("applyExclusiveSlotSelection", () => {
|
||||
selectedId: "custom",
|
||||
});
|
||||
|
||||
expect(result.changed).toBe(false);
|
||||
expect(result.warnings).toHaveLength(0);
|
||||
expectUnchangedSelection(result);
|
||||
expect(result.config).toBe(config);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,24 +3,11 @@ import { describe, expect, it } from "vitest";
|
||||
import { withPathResolutionEnv } from "../test-utils/env.js";
|
||||
import { formatPluginSourceForTable, resolvePluginSourceRoots } from "./source-display.js";
|
||||
|
||||
function createPluginSourceRoots() {
|
||||
const stockRoot = path.resolve(
|
||||
path.sep,
|
||||
"opt",
|
||||
"homebrew",
|
||||
"lib",
|
||||
"node_modules",
|
||||
"openclaw",
|
||||
"extensions",
|
||||
);
|
||||
const globalRoot = path.resolve(path.sep, "Users", "x", ".openclaw", "extensions");
|
||||
const workspaceRoot = path.resolve(path.sep, "Users", "x", "ws", ".openclaw", "extensions");
|
||||
return {
|
||||
stock: stockRoot,
|
||||
global: globalRoot,
|
||||
workspace: workspaceRoot,
|
||||
};
|
||||
}
|
||||
const PLUGIN_SOURCE_ROOTS = {
|
||||
stock: path.resolve(path.sep, "opt", "homebrew", "lib", "node_modules", "openclaw", "extensions"),
|
||||
global: path.resolve(path.sep, "Users", "x", ".openclaw", "extensions"),
|
||||
workspace: path.resolve(path.sep, "Users", "x", "ws", ".openclaw", "extensions"),
|
||||
};
|
||||
|
||||
function expectFormattedSource(params: {
|
||||
origin: "bundled" | "workspace" | "global";
|
||||
@@ -30,13 +17,12 @@ function expectFormattedSource(params: {
|
||||
expectedValue: string;
|
||||
expectedRootKey: "stock" | "workspace" | "global";
|
||||
}) {
|
||||
const roots = createPluginSourceRoots();
|
||||
const out = formatPluginSourceForTable(
|
||||
{
|
||||
origin: params.origin,
|
||||
source: path.join(roots[params.sourceKey], params.dirName, params.fileName),
|
||||
source: path.join(PLUGIN_SOURCE_ROOTS[params.sourceKey], params.dirName, params.fileName),
|
||||
},
|
||||
roots,
|
||||
PLUGIN_SOURCE_ROOTS,
|
||||
);
|
||||
expect(out.value).toBe(params.expectedValue);
|
||||
expect(out.rootKey).toBe(params.expectedRootKey);
|
||||
|
||||
Reference in New Issue
Block a user