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; }; function createMockModule(): LanceDbModule { return { connect: vi.fn(), } as unknown as LanceDbModule; } function createLoader( overrides: { env?: NodeJS.ProcessEnv; importBundled?: () => Promise; importResolved?: (resolvedPath: string) => Promise; resolveRuntimeEntry?: (params: { runtimeDir: string; manifest: RuntimeManifest; }) => string | null; installRuntime?: (params: { runtimeDir: string; manifest: RuntimeManifest; env: NodeJS.ProcessEnv; logger?: LanceDbRuntimeLogger; }) => Promise; } = {}, ) { 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); }); });