mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 15:24:46 +00:00
121 lines
4.3 KiB
TypeScript
121 lines
4.3 KiB
TypeScript
import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
|
import type { PluginCandidate, PluginDiscoveryResult } from "./discovery.js";
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.resetModules();
|
|
vi.doUnmock("./discovery.js");
|
|
vi.doUnmock("./installed-plugin-index-record-reader.js");
|
|
});
|
|
|
|
const ENV: NodeJS.ProcessEnv = { HOME: "/tmp/openclaw-test-home" };
|
|
let loadCase = 0;
|
|
|
|
const RECORDS: Record<string, PluginInstallRecord> = {
|
|
weixin: {
|
|
source: "npm",
|
|
spec: "@tencent-weixin/openclaw-weixin@2.3.7",
|
|
installPath:
|
|
"/tmp/openclaw-test-home/.openclaw/npm/node_modules/@tencent-weixin/openclaw-weixin",
|
|
} as PluginInstallRecord,
|
|
};
|
|
|
|
function emptyDiscoveryResult(): PluginDiscoveryResult {
|
|
return {
|
|
candidates: [] as PluginCandidate[],
|
|
diagnostics: [],
|
|
};
|
|
}
|
|
|
|
async function loadWithMocks(params: {
|
|
loadRecords?: (env: NodeJS.ProcessEnv | undefined) => Record<string, PluginInstallRecord>;
|
|
}): Promise<{
|
|
module: typeof import("./channel-catalog-registry.js");
|
|
discoverSpy: ReturnType<typeof vi.fn>;
|
|
loadRecordsSpy: ReturnType<typeof vi.fn>;
|
|
}> {
|
|
const discoverSpy = vi.fn(() => emptyDiscoveryResult());
|
|
const loadRecordsSpy = vi.fn((opts: { env?: NodeJS.ProcessEnv } = {}) => {
|
|
return params.loadRecords ? params.loadRecords(opts.env) : RECORDS;
|
|
});
|
|
|
|
vi.doMock("./discovery.js", () => ({ discoverOpenClawPlugins: discoverSpy }));
|
|
vi.doMock("./installed-plugin-index-record-reader.js", () => ({
|
|
loadInstalledPluginIndexInstallRecordsSync: loadRecordsSpy,
|
|
}));
|
|
|
|
const module = await importFreshModule<typeof import("./channel-catalog-registry.js")>(
|
|
import.meta.url,
|
|
`./channel-catalog-registry.js?case=${++loadCase}`,
|
|
);
|
|
return { module, discoverSpy, loadRecordsSpy };
|
|
}
|
|
|
|
describe("listChannelCatalogEntries", () => {
|
|
it("forwards lazily loaded install records to discovery when origin is unspecified", async () => {
|
|
const { module, discoverSpy, loadRecordsSpy } = await loadWithMocks({});
|
|
|
|
module.listChannelCatalogEntries({ env: ENV });
|
|
|
|
expect(loadRecordsSpy).toHaveBeenCalledTimes(1);
|
|
expect(loadRecordsSpy).toHaveBeenCalledWith({ env: ENV });
|
|
expect(discoverSpy).toHaveBeenCalledTimes(1);
|
|
expect(discoverSpy.mock.calls[0][0]).toMatchObject({
|
|
env: ENV,
|
|
installRecords: RECORDS,
|
|
});
|
|
});
|
|
|
|
it("skips ledger lookup when origin is 'bundled' and omits installRecords", async () => {
|
|
const { module, discoverSpy, loadRecordsSpy } = await loadWithMocks({});
|
|
|
|
module.listChannelCatalogEntries({ origin: "bundled", env: ENV });
|
|
|
|
expect(loadRecordsSpy).not.toHaveBeenCalled();
|
|
expect(discoverSpy).toHaveBeenCalledTimes(1);
|
|
expect(discoverSpy.mock.calls[0][0]).not.toHaveProperty("installRecords");
|
|
});
|
|
|
|
it("uses caller-supplied install records verbatim and does not load the ledger", async () => {
|
|
const { module, discoverSpy, loadRecordsSpy } = await loadWithMocks({});
|
|
const supplied: Record<string, PluginInstallRecord> = {
|
|
slack: {
|
|
source: "npm",
|
|
spec: "@openclaw/slack@1.0.0",
|
|
} as PluginInstallRecord,
|
|
};
|
|
|
|
module.listChannelCatalogEntries({ env: ENV, installRecords: supplied });
|
|
|
|
expect(loadRecordsSpy).not.toHaveBeenCalled();
|
|
expect(discoverSpy.mock.calls[0][0]).toMatchObject({ installRecords: supplied });
|
|
});
|
|
|
|
it("omits installRecords from discovery when the ledger is empty", async () => {
|
|
const { module, discoverSpy, loadRecordsSpy } = await loadWithMocks({
|
|
loadRecords: () => ({}),
|
|
});
|
|
|
|
module.listChannelCatalogEntries({ env: ENV });
|
|
|
|
expect(loadRecordsSpy).toHaveBeenCalledTimes(1);
|
|
expect(discoverSpy.mock.calls[0][0]).not.toHaveProperty("installRecords");
|
|
});
|
|
|
|
it("treats ledger read errors as a soft fallback (no installRecords propagated)", async () => {
|
|
const { module, discoverSpy, loadRecordsSpy } = await loadWithMocks({
|
|
loadRecords: () => {
|
|
throw new Error("simulated reader failure");
|
|
},
|
|
});
|
|
|
|
expect(module.listChannelCatalogEntries({ env: ENV })).toStrictEqual([]);
|
|
|
|
expect(loadRecordsSpy).toHaveBeenCalledTimes(1);
|
|
expect(discoverSpy).toHaveBeenCalledTimes(1);
|
|
expect(discoverSpy.mock.calls[0][0]).not.toHaveProperty("installRecords");
|
|
});
|
|
});
|