Files
openclaw/src/plugins/manifest-model-id-normalization.test.ts
Peter Steinberger 2f7f543d9a fix: keep model switching and config recovery reliable under load (#114679)
* fix: stabilize concurrent model and config snapshots

* fix: retire obsolete plugin metadata cleanup facade

* fix: route Codex prewarm regression exactly once
2026-07-27 16:36:50 -04:00

161 lines
5.4 KiB
TypeScript

// Verifies model IDs declared by plugin manifests are normalized.
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { captureEnv, deleteTestEnvValue, setTestEnvValue } from "../test-utils/env.js";
import { writePersistedInstalledPluginIndexSync } from "./installed-plugin-index-store.js";
import { listOpenClawPluginManifestMetadata } from "./manifest-metadata-scan.js";
import { normalizeProviderModelIdWithManifest } from "./manifest-model-id-normalization.js";
import { clearPluginMetadataLifecycleCaches } from "./plugin-metadata-lifecycle.js";
import { resetPluginRuntimeStateForTest } from "./runtime.js";
const tempDirs: string[] = [];
const testEnvSnapshot = captureEnv([
"OPENCLAW_STATE_DIR",
"OPENCLAW_HOME",
"OPENCLAW_DISABLE_BUNDLED_PLUGINS",
"OPENCLAW_BUNDLED_PLUGINS_DIR",
]);
function restoreEnv(): void {
testEnvSnapshot.restore();
}
function makeTempDir(): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-model-id-normalization-"));
tempDirs.push(dir);
return dir;
}
function writeInstallIndex(params: { stateDir: string; pluginDir: string }): void {
writePersistedInstalledPluginIndexSync(
{
version: 1,
hostContractVersion: "test",
compatRegistryVersion: "test",
migrationVersion: 1,
policyHash: "test",
generatedAtMs: 1,
installRecords: {},
plugins: [
{
pluginId: "normalizer",
manifestPath: path.join(params.pluginDir, "openclaw.plugin.json"),
manifestHash: "normalizer-manifest",
rootDir: params.pluginDir,
origin: "global",
enabled: true,
startup: {
sidecar: false,
memory: false,
deferConfiguredChannelFullLoadUntilAfterListen: false,
agentHarnesses: [],
},
compat: [],
},
],
diagnostics: [],
},
{ stateDir: params.stateDir },
);
}
function writeNormalizerManifest(params: { pluginDir: string; prefix: string }): void {
fs.mkdirSync(params.pluginDir, { recursive: true });
fs.writeFileSync(
path.join(params.pluginDir, "index.ts"),
"throw new Error('runtime entry should not load while reading manifests');\n",
"utf-8",
);
fs.writeFileSync(
path.join(params.pluginDir, "openclaw.plugin.json"),
JSON.stringify({
id: "normalizer",
configSchema: { type: "object" },
providers: ["demo"],
modelIdNormalization: {
providers: {
demo: {
prefixWhenBare: params.prefix,
},
},
},
}),
"utf-8",
);
}
function normalizeDemoModel(modelId = "demo-model"): string | undefined {
return normalizeProviderModelIdWithManifest({
provider: "demo",
context: { provider: "demo", modelId },
});
}
describe("manifest model id normalization", () => {
beforeEach(() => {
resetPluginRuntimeStateForTest();
clearPluginMetadataLifecycleCaches();
});
afterEach(() => {
resetPluginRuntimeStateForTest();
clearPluginMetadataLifecycleCaches();
restoreEnv();
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
it("reflects manifest and state-dir changes without a prepared snapshot", () => {
const stateDirA = makeTempDir();
const pluginDirA = path.join(stateDirA, "extensions", "normalizer");
writeInstallIndex({ stateDir: stateDirA, pluginDir: pluginDirA });
writeNormalizerManifest({ pluginDir: pluginDirA, prefix: "alpha" });
setTestEnvValue("OPENCLAW_STATE_DIR", stateDirA);
deleteTestEnvValue("OPENCLAW_HOME");
setTestEnvValue("OPENCLAW_DISABLE_BUNDLED_PLUGINS", "1");
deleteTestEnvValue("OPENCLAW_BUNDLED_PLUGINS_DIR");
expect(normalizeDemoModel()).toBe("alpha/demo-model");
writeNormalizerManifest({ pluginDir: pluginDirA, prefix: "bravo-local" });
expect(normalizeDemoModel()).toBe("bravo-local/demo-model");
const stateDirB = makeTempDir();
const pluginDirB = path.join(stateDirB, "extensions", "normalizer");
writeInstallIndex({ stateDir: stateDirB, pluginDir: pluginDirB });
writeNormalizerManifest({ pluginDir: pluginDirB, prefix: "charlie" });
setTestEnvValue("OPENCLAW_STATE_DIR", stateDirB);
clearPluginMetadataLifecycleCaches();
expect(normalizeDemoModel()).toBe("charlie/demo-model");
});
it("reuses manifest metadata while file fingerprints are unchanged", () => {
const stateDir = makeTempDir();
const pluginDir = path.join(stateDir, "extensions", "normalizer");
writeInstallIndex({ stateDir, pluginDir });
writeNormalizerManifest({ pluginDir, prefix: "alpha" });
setTestEnvValue("OPENCLAW_STATE_DIR", stateDir);
deleteTestEnvValue("OPENCLAW_HOME");
setTestEnvValue("OPENCLAW_DISABLE_BUNDLED_PLUGINS", "1");
deleteTestEnvValue("OPENCLAW_BUNDLED_PLUGINS_DIR");
// The scan also lists source-checkout extensions/ manifests when tests run
// from a repo checkout, so only pin the record for the plugin under test.
const listNormalizerRecords = () =>
listOpenClawPluginManifestMetadata(process.env).filter(
(record) => record.pluginDir === pluginDir,
);
const firstRecords = listNormalizerRecords();
const secondRecords = listNormalizerRecords();
expect(firstRecords).toHaveLength(1);
expect(secondRecords).toHaveLength(1);
expect(secondRecords[0]).toBe(firstRecords[0]);
});
});