mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-03 06:11:36 +00:00
* test: remove redundant followup goal integration * test: cover CLI help table in process * test: inject usage cost settle timing * test: reuse rendered plugin SDK baseline * test: reuse bundled provider registrations * test: isolate startup quarantine mapping * test: inject session patch auth metadata * test: avoid CLI help catch shadowing * test(cli): keep gateway dependencies internal * test(auto-reply): stabilize cold followup setup
170 lines
6.4 KiB
TypeScript
170 lines
6.4 KiB
TypeScript
/**
|
|
* Tests the plugin SDK public API baseline.
|
|
*/
|
|
import path from "node:path";
|
|
import { beforeAll, describe, expect, it } from "vitest";
|
|
import { publicPluginSdkEntrypoints } from "../../scripts/lib/plugin-sdk-entries.mjs";
|
|
import {
|
|
computePluginSdkApiBaselineHashFileContent,
|
|
listPluginSdkApiBaselineEntrypoints,
|
|
normalizePluginSdkApiDeclarationText,
|
|
normalizePluginSdkApiSourcePath,
|
|
renderPluginSdkApiBaseline,
|
|
renderPluginSdkApiBaselineModules,
|
|
type PluginSdkApiBaselineRender,
|
|
} from "./api-baseline.js";
|
|
|
|
const TEST_ENTRYPOINTS = [
|
|
"agent-harness-runtime",
|
|
"approval-gateway-runtime",
|
|
"channel-policy",
|
|
"core",
|
|
"infra-runtime",
|
|
"provider-auth",
|
|
"provider-catalog-live-runtime",
|
|
"provider-oauth-runtime",
|
|
"provider-selection-runtime",
|
|
"provider-web-search-config-contract",
|
|
"realtime-voice",
|
|
"sqlite-runtime-testing",
|
|
] as const;
|
|
|
|
describe("Plugin SDK API baseline", () => {
|
|
let rendered: PluginSdkApiBaselineRender;
|
|
|
|
beforeAll(async () => {
|
|
rendered = await renderPluginSdkApiBaseline({ entrypoints: TEST_ENTRYPOINTS });
|
|
});
|
|
|
|
it("normalizes declaration import paths to repo-relative paths", () => {
|
|
const repoRoot = process.cwd();
|
|
const modelCatalogPath = path.join(repoRoot, "src", "agents", "agent-model-discovery");
|
|
const declaration = `export function setModelCatalogImportForTest(loader?: (() => Promise<typeof import("${modelCatalogPath}", { with: { "resolution-mode": "import" } })>) | undefined): void;`;
|
|
|
|
const normalized = normalizePluginSdkApiDeclarationText(repoRoot, declaration);
|
|
|
|
expect(normalized).not.toContain(repoRoot);
|
|
expect(normalized).toContain(
|
|
'import("src/agents/agent-model-discovery", { with: { "resolution-mode": "import" } })',
|
|
);
|
|
});
|
|
|
|
it("normalizes dependency source paths to stable node_modules paths", () => {
|
|
const repoRoot = path.join(path.sep, "workspace", "openclaw-worktree");
|
|
const linkedDependencyPath = path.join(
|
|
path.sep,
|
|
"workspace",
|
|
"openclaw",
|
|
"node_modules",
|
|
"@openclaw",
|
|
"fs-safe",
|
|
"dist",
|
|
"secret-file.d.ts",
|
|
);
|
|
const pnpmDependencyPath = path.join(
|
|
repoRoot,
|
|
"node_modules",
|
|
".pnpm",
|
|
"@openclaw+fs-safe@1.0.0",
|
|
"node_modules",
|
|
"@openclaw",
|
|
"fs-safe",
|
|
"dist",
|
|
"secret-file.d.ts",
|
|
);
|
|
|
|
expect(normalizePluginSdkApiSourcePath(repoRoot, linkedDependencyPath)).toBe(
|
|
"node_modules/@openclaw/fs-safe/dist/secret-file.d.ts",
|
|
);
|
|
expect(normalizePluginSdkApiSourcePath(repoRoot, pnpmDependencyPath)).toBe(
|
|
"node_modules/@openclaw/fs-safe/dist/secret-file.d.ts",
|
|
);
|
|
});
|
|
|
|
it("keeps repo source paths relative when a parent directory is named node_modules", () => {
|
|
const repoRoot = path.join(path.sep, "workspace", "node_modules", "openclaw");
|
|
const sourcePath = path.join(repoRoot, "src", "plugin-sdk", "core.ts");
|
|
|
|
expect(normalizePluginSdkApiSourcePath(repoRoot, sourcePath)).toBe("src/plugin-sdk/core.ts");
|
|
});
|
|
|
|
it("renders complete declarations for the canonical public entrypoint inventory", () => {
|
|
expect(listPluginSdkApiBaselineEntrypoints()).toEqual(publicPluginSdkEntrypoints);
|
|
|
|
const findDeclaration = (exportName: string) =>
|
|
rendered.baseline.modules
|
|
.flatMap((moduleSurface) => moduleSurface.exports)
|
|
.find(
|
|
(exportSurface) =>
|
|
exportSurface.exportName === exportName && exportSurface.declaration !== null,
|
|
)?.declaration;
|
|
|
|
expect(rendered.baseline.modules.find((entry) => entry.entrypoint === "infra-runtime")).toEqual(
|
|
expect.objectContaining({
|
|
category: null,
|
|
importSpecifier: "openclaw/plugin-sdk/infra-runtime",
|
|
}),
|
|
);
|
|
expect(findDeclaration("OAuthProviderInterface")).toContain("readonly id: OAuthProviderId;");
|
|
expect(findDeclaration("OAuthProviderInterface")).toContain(
|
|
"login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;",
|
|
);
|
|
expect(findDeclaration("LiveModelCatalogHttpError")).toContain("readonly status: number;");
|
|
expect(findDeclaration("LiveModelCatalogHttpError")).toContain(
|
|
"constructor(providerId: string, status: number);",
|
|
);
|
|
expect(findDeclaration("LiveModelCatalogHttpError")).not.toContain("super(");
|
|
expect(findDeclaration("LiveModelRowProjection")).toContain(
|
|
"export type LiveModelRowProjection",
|
|
);
|
|
expect(findDeclaration("ApprovalResolveResult")).not.toContain("see source");
|
|
expect(findDeclaration("RealtimeVoiceAgentConsultRuntime")).not.toContain("see source");
|
|
expect(findDeclaration("createWebSearchProviderContractFields")).toContain(
|
|
"export function createWebSearchProviderContractFields(",
|
|
);
|
|
expect(findDeclaration("createWebSearchProviderContractFields")).not.toContain(
|
|
"createBaseWebSearchProviderContractFields",
|
|
);
|
|
expect(findDeclaration("OPENCLAW_VERSION")).toContain("export const OPENCLAW_VERSION:");
|
|
expect(findDeclaration("SqliteTrajectoryRuntimeEventForTest")).toContain(
|
|
"export type SqliteTrajectoryRuntimeEventForTest =",
|
|
);
|
|
expect(findDeclaration("ProviderSelection")).toContain(
|
|
"export type ProviderSelection<TProvider> =",
|
|
);
|
|
expect(rendered.json).not.toContain('"line":');
|
|
expect(rendered.jsonl).not.toContain('"sourceLine":');
|
|
});
|
|
|
|
it("renders snapshots independently of entrypoint discovery order", () => {
|
|
const reverse = renderPluginSdkApiBaselineModules(rendered.baseline.modules.toReversed());
|
|
|
|
expect(reverse.json).toBe(rendered.json);
|
|
expect(reverse.jsonl).toBe(rendered.jsonl);
|
|
});
|
|
|
|
it("hashes entrypoints independently so unrelated API changes merge", () => {
|
|
const target = rendered.baseline.modules[0];
|
|
expect(target?.exports.length).toBeGreaterThan(0);
|
|
const changed = renderPluginSdkApiBaselineModules(
|
|
rendered.baseline.modules.map((moduleSurface) =>
|
|
moduleSurface === target
|
|
? {
|
|
...moduleSurface,
|
|
exports: moduleSurface.exports.map((exportSurface, index) =>
|
|
index === 0
|
|
? { ...exportSurface, declaration: `${exportSurface.declaration ?? ""} changed` }
|
|
: exportSurface,
|
|
),
|
|
}
|
|
: moduleSurface,
|
|
),
|
|
);
|
|
const before = computePluginSdkApiBaselineHashFileContent(rendered).split("\n");
|
|
const after = computePluginSdkApiBaselineHashFileContent(changed).split("\n");
|
|
|
|
expect(after[0]).not.toBe(before[0]);
|
|
expect(after.slice(1)).toEqual(before.slice(1));
|
|
});
|
|
});
|