Files
openclaw/extensions/copilot/src/sdk-loader.test.ts
Peter Steinberger a679cda0c9 refactor(copilot): trim internal exports (#107730)
* refactor(copilot): trim internal exports

* chore(deadcode): refresh export baseline
2026-07-14 12:23:35 -07:00

240 lines
7.8 KiB
TypeScript
Executable File

// Copilot tests cover sdk loader plugin behavior.
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { loadCopilotSdk } from "./sdk-loader.js";
const COPILOT_SDK_SPEC = "@github/copilot-sdk@1.0.5";
const FAKE_SDK = {
CopilotClient: class FakeCopilotClient {
_fake = true;
},
} as unknown as typeof import("@github/copilot-sdk");
describe("sdk-loader", () => {
it("returns the primary import when it succeeds", async () => {
const primaryImport = vi.fn(async () => FAKE_SDK);
const fallbackImport = vi.fn(async () => {
throw new Error("should not be called");
});
const sdk = await loadCopilotSdk({
cache: false,
fallbackDir: "/dev/null/does-not-exist",
primaryImport,
fallbackImport,
});
expect(sdk).toBe(FAKE_SDK);
expect(primaryImport).toHaveBeenCalledTimes(1);
expect(fallbackImport).not.toHaveBeenCalled();
});
it("falls back to the on-demand install location when primary import fails", async () => {
const tmp = mkdtempSync(path.join(tmpdir(), "copilot-sdk-loader-"));
try {
// Materialize the fallback path so the existsSync check passes.
const fallbackPath = path.join(tmp, "node_modules", "@github", "copilot-sdk");
mkdirSync(fallbackPath, { recursive: true });
writeFileSync(path.join(fallbackPath, "index.js"), "// placeholder");
const primaryImport = vi.fn(async () => {
const err = new Error("Cannot find module '@github/copilot-sdk'") as Error & {
code: string;
};
err.code = "ERR_MODULE_NOT_FOUND";
throw err;
});
const fallbackImport = vi.fn(async (abs: string) => {
expect(abs).toBe(fallbackPath);
return FAKE_SDK;
});
const sdk = await loadCopilotSdk({
cache: false,
fallbackDir: tmp,
primaryImport,
fallbackImport,
});
expect(sdk).toBe(FAKE_SDK);
expect(primaryImport).toHaveBeenCalledTimes(1);
expect(fallbackImport).toHaveBeenCalledTimes(1);
} finally {
rmSync(tmp, { recursive: true, force: true });
}
});
it("default fallback importer resolves and imports the installed SDK entry", async () => {
// Exercise the real default fallback importer (no fallbackImport injection)
// to prove it imports a concrete entry file rather than the package
// directory, which Node ESM would reject with ERR_UNSUPPORTED_DIR_IMPORT.
const tmp = mkdtempSync(path.join(tmpdir(), "copilot-sdk-loader-default-"));
try {
const pkgDir = path.join(tmp, "node_modules", "@github", "copilot-sdk");
mkdirSync(pkgDir, { recursive: true });
writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify({
name: "@github/copilot-sdk",
version: "0.0.0-test",
main: "./index.cjs",
}),
);
writeFileSync(
path.join(pkgDir, "index.cjs"),
"module.exports = { openclawDefaultImporterSentinel: true };",
);
const primaryImport = vi.fn(async () => {
const err = new Error("Cannot find module '@github/copilot-sdk'") as Error & {
code: string;
};
err.code = "ERR_MODULE_NOT_FOUND";
throw err;
});
const sdk = (await loadCopilotSdk({
cache: false,
fallbackDir: tmp,
primaryImport,
// Intentionally NOT injecting fallbackImport; exercise the default.
})) as unknown as { openclawDefaultImporterSentinel?: boolean };
expect(sdk.openclawDefaultImporterSentinel).toBe(true);
expect(primaryImport).toHaveBeenCalledTimes(1);
} finally {
rmSync(tmp, { recursive: true, force: true });
}
});
it("throws an actionable error with plugin install instructions when both probes fail", async () => {
const primaryImport = vi.fn(async () => {
throw new Error("Cannot find module '@github/copilot-sdk'");
});
const fallbackImport = vi.fn(async () => {
throw new Error("should not be called when fallback dir does not exist");
});
await expect(
loadCopilotSdk({
cache: false,
fallbackDir: path.join(tmpdir(), "copilot-sdk-loader-missing-" + Date.now()),
primaryImport,
fallbackImport,
}),
).rejects.toMatchObject({
code: "COPILOT_SDK_MISSING",
message: expect.stringContaining("openclaw plugins install @openclaw/copilot"),
});
expect(fallbackImport).not.toHaveBeenCalled();
});
it("error message includes the fallback path and underlying primary error", async () => {
const primaryImport = vi.fn(async () => {
throw new Error("primary boom");
});
const fallbackDir = path.join(tmpdir(), "copilot-sdk-loader-missing-" + Date.now());
let captured: Error | undefined;
try {
await loadCopilotSdk({
cache: false,
fallbackDir,
primaryImport,
});
} catch (err) {
captured = err as Error;
}
expect(captured).toBeDefined();
const message = captured?.message ?? "";
expect(message).toContain("primary boom");
expect(message).toContain(path.join(fallbackDir, "node_modules", "@github", "copilot-sdk"));
expect(message).toContain(COPILOT_SDK_SPEC);
expect(message).toContain("openclaw plugins install @openclaw/copilot");
});
it("caches successful loads across calls when cache is enabled", async () => {
vi.resetModules();
const { loadCopilotSdk: loadFreshCopilotSdk } = await import("./sdk-loader.js");
const primaryImport = vi.fn(async () => FAKE_SDK);
const a = await loadFreshCopilotSdk({
primaryImport,
fallbackDir: "/dev/null/does-not-exist",
});
const b = await loadFreshCopilotSdk({
primaryImport,
fallbackDir: "/dev/null/does-not-exist",
});
expect(a).toBe(FAKE_SDK);
expect(b).toBe(FAKE_SDK);
expect(primaryImport).toHaveBeenCalledTimes(1);
});
it("does not poison the cache after a failed load", async () => {
vi.resetModules();
const { loadCopilotSdk: loadFreshCopilotSdk } = await import("./sdk-loader.js");
const primaryImport = vi
.fn<typeof Promise>()
.mockRejectedValueOnce(new Error("first boom"))
.mockResolvedValueOnce(FAKE_SDK);
await expect(
loadFreshCopilotSdk({
primaryImport: primaryImport as unknown as () => Promise<
typeof import("@github/copilot-sdk")
>,
fallbackDir: "/dev/null/does-not-exist",
}),
).rejects.toBeInstanceOf(Error);
const sdk = await loadFreshCopilotSdk({
primaryImport: primaryImport as unknown as () => Promise<
typeof import("@github/copilot-sdk")
>,
fallbackDir: "/dev/null/does-not-exist",
});
expect(sdk).toBe(FAKE_SDK);
expect(primaryImport).toHaveBeenCalledTimes(2);
});
it("resolves the fallback install from OPENCLAW_STATE_DIR", async () => {
const stateDir = mkdtempSync(path.join(tmpdir(), "copilot-sdk-loader-state-"));
try {
const fallbackPath = path.join(
stateDir,
"npm-runtime",
"copilot",
"node_modules",
"@github",
"copilot-sdk",
);
mkdirSync(fallbackPath, { recursive: true });
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
const fallbackImport = vi.fn(async (absolutePath: string) => {
expect(absolutePath).toBe(fallbackPath);
return FAKE_SDK;
});
await expect(
loadCopilotSdk({
cache: false,
primaryImport: async () => {
throw new Error("primary missing");
},
fallbackImport,
}),
).resolves.toBe(FAKE_SDK);
expect(fallbackImport).toHaveBeenCalledTimes(1);
} finally {
vi.unstubAllEnvs();
rmSync(stateDir, { recursive: true, force: true });
}
});
});