openclaw/extensions/memory-lancedb/lancedb-runtime.test.ts

177 lines
5.7 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { createLanceDbRuntimeLoader, type LanceDbRuntimeLogger } from "./lancedb-runtime.js";
const TEST_RUNTIME_MANIFEST = {
name: "openclaw-memory-lancedb-runtime",
private: true as const,
type: "module" as const,
dependencies: {
"@lancedb/lancedb": "^0.27.1",
},
};
type LanceDbModule = typeof import("@lancedb/lancedb");
type RuntimeManifest = {
name: string;
private: true;
type: "module";
dependencies: Record<string, string>;
};
function createMockModule(): LanceDbModule {
return {
connect: vi.fn(),
} as unknown as LanceDbModule;
}
function createLoader(
overrides: {
env?: NodeJS.ProcessEnv;
importBundled?: () => Promise<LanceDbModule>;
importResolved?: (resolvedPath: string) => Promise<LanceDbModule>;
resolveRuntimeEntry?: (params: {
runtimeDir: string;
manifest: RuntimeManifest;
}) => string | null;
installRuntime?: (params: {
runtimeDir: string;
manifest: RuntimeManifest;
env: NodeJS.ProcessEnv;
logger?: LanceDbRuntimeLogger;
}) => Promise<string>;
} = {},
) {
return createLanceDbRuntimeLoader({
env: overrides.env ?? ({} as NodeJS.ProcessEnv),
resolveStateDir: () => "/tmp/openclaw-state",
runtimeManifest: TEST_RUNTIME_MANIFEST,
importBundled:
overrides.importBundled ??
(async () => {
throw new Error("Cannot find package '@lancedb/lancedb'");
}),
importResolved: overrides.importResolved ?? (async () => createMockModule()),
resolveRuntimeEntry: overrides.resolveRuntimeEntry ?? (() => null),
installRuntime:
overrides.installRuntime ??
(async ({ runtimeDir }: { runtimeDir: string }) =>
`${runtimeDir}/node_modules/@lancedb/lancedb/index.js`),
});
}
describe("lancedb runtime loader", () => {
it("uses the bundled module when it is already available", async () => {
const bundledModule = createMockModule();
const importBundled = vi.fn(async () => bundledModule);
const importResolved = vi.fn(async () => createMockModule());
const resolveRuntimeEntry = vi.fn(() => null);
const installRuntime = vi.fn(async () => "/tmp/openclaw-state/plugin-runtimes/lancedb.js");
const loader = createLoader({
importBundled,
importResolved,
resolveRuntimeEntry,
installRuntime,
});
await expect(loader.load()).resolves.toBe(bundledModule);
expect(resolveRuntimeEntry).not.toHaveBeenCalled();
expect(installRuntime).not.toHaveBeenCalled();
expect(importResolved).not.toHaveBeenCalled();
});
it("reuses an existing user runtime install before attempting a reinstall", async () => {
const runtimeModule = createMockModule();
const importResolved = vi.fn(async () => runtimeModule);
const resolveRuntimeEntry = vi.fn(
() => "/tmp/openclaw-state/plugin-runtimes/memory-lancedb/runtime-entry.js",
);
const installRuntime = vi.fn(
async () => "/tmp/openclaw-state/plugin-runtimes/memory-lancedb/runtime-entry.js",
);
const loader = createLoader({
importResolved,
resolveRuntimeEntry,
installRuntime,
});
await expect(loader.load()).resolves.toBe(runtimeModule);
expect(resolveRuntimeEntry).toHaveBeenCalledWith(
expect.objectContaining({
runtimeDir: "/tmp/openclaw-state/plugin-runtimes/memory-lancedb/lancedb",
}),
);
expect(installRuntime).not.toHaveBeenCalled();
});
it("installs LanceDB into user state when the bundled runtime is unavailable", async () => {
const runtimeModule = createMockModule();
const logger: LanceDbRuntimeLogger = {
warn: vi.fn(),
info: vi.fn(),
};
const importResolved = vi.fn(async () => runtimeModule);
const resolveRuntimeEntry = vi.fn(() => null);
const installRuntime = vi.fn(
async ({ runtimeDir }: { runtimeDir: string }) =>
`${runtimeDir}/node_modules/@lancedb/lancedb/index.js`,
);
const loader = createLoader({
importResolved,
resolveRuntimeEntry,
installRuntime,
});
await expect(loader.load(logger)).resolves.toBe(runtimeModule);
expect(installRuntime).toHaveBeenCalledWith(
expect.objectContaining({
runtimeDir: "/tmp/openclaw-state/plugin-runtimes/memory-lancedb/lancedb",
manifest: TEST_RUNTIME_MANIFEST,
}),
);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining(
"installing runtime deps under /tmp/openclaw-state/plugin-runtimes/memory-lancedb/lancedb",
),
);
});
it("fails fast in nix mode instead of attempting auto-install", async () => {
const installRuntime = vi.fn(
async ({ runtimeDir }: { runtimeDir: string }) =>
`${runtimeDir}/node_modules/@lancedb/lancedb/index.js`,
);
const loader = createLoader({
env: { OPENCLAW_NIX_MODE: "1" } as NodeJS.ProcessEnv,
installRuntime,
});
await expect(loader.load()).rejects.toThrow(
"memory-lancedb: failed to load LanceDB and Nix mode disables auto-install.",
);
expect(installRuntime).not.toHaveBeenCalled();
});
it("clears the cached failure so later calls can retry the install", async () => {
const runtimeModule = createMockModule();
const installRuntime = vi
.fn()
.mockRejectedValueOnce(new Error("network down"))
.mockResolvedValueOnce(
"/tmp/openclaw-state/plugin-runtimes/memory-lancedb/lancedb/node_modules/@lancedb/lancedb/index.js",
);
const importResolved = vi.fn(async () => runtimeModule);
const loader = createLoader({
installRuntime,
importResolved,
});
await expect(loader.load()).rejects.toThrow("network down");
await expect(loader.load()).resolves.toBe(runtimeModule);
expect(installRuntime).toHaveBeenCalledTimes(2);
});
});