Files
openclaw/test/scripts/doctor-config-preflight-plugin-index.built-cli.e2e.test.ts
Vincent Koc e4aab26d01 fix(cli): avoid repeated migration scans during local agent turns (#119051)
* fix(cli): avoid repeated state migration scans

* fix(build): preserve source runtime provenance

* fix(cli): persist state checkpoint before plugin convergence

* fix(cli): invalidate migration checkpoints on input changes

* fix(plugins): refresh changed doctor contracts

* fix(plugins): preserve warm Doctor contract indexes

* test(plugins): align manifest registry fixtures

* test(cli): isolate ACP process state

* fix(doctor): persist refreshed plugin indexes

* fix(doctor): verify persisted plugin index from disk

* fix(doctor): fence plugin index persistence

* fix(doctor): retain validated migration lease

* fix(plugins): canonicalize persisted build metadata

* fix(plugins): preserve config policy during install repair
2026-08-04 20:28:16 +08:00

122 lines
4.3 KiB
TypeScript

// Built-CLI proof for durable Doctor plugin-index refresh during gateway startup.
import fs from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../../src/config/types.openclaw.js";
import { hasActiveStartupMigrationLease } from "../../src/infra/startup-migration-checkpoint.js";
import {
readPersistedInstalledPluginIndexSync,
writePersistedInstalledPluginIndexSync,
} from "../../src/plugins/installed-plugin-index-store.js";
import { clearPluginMetadataLifecycleCaches } from "../../src/plugins/plugin-metadata-lifecycle.js";
import { loadPluginMetadataSnapshot } from "../../src/plugins/plugin-metadata-snapshot.js";
import { writeManagedNpmPlugin } from "../../src/plugins/test-helpers/managed-npm-plugin.js";
import { closeOpenClawStateDatabaseForTest } from "../../src/state/openclaw-state-db.js";
import {
createOpenClawTestInstance,
type OpenClawTestInstance,
} from "../helpers/openclaw-test-instance.js";
const instances: OpenClawTestInstance[] = [];
afterEach(async () => {
await Promise.all(instances.splice(0).map((instance) => instance.cleanup()));
clearPluginMetadataLifecycleCaches();
closeOpenClawStateDatabaseForTest();
});
describe("Doctor plugin index persistence built CLI proof", () => {
it("starts after replacing and verifying a stale persisted Doctor index", async () => {
const instance = await createOpenClawTestInstance({
name: "doctor-plugin-index-persistence",
env: {
OPENCLAW_TEST_FAST: "1",
},
startTimeoutMs: 90_000,
});
instances.push(instance);
const config = JSON.parse(fs.readFileSync(instance.configPath, "utf8")) as OpenClawConfig;
const pluginId = "legacy-doctor-index";
const pluginDir = writeManagedNpmPlugin({
stateDir: instance.stateDir,
packageName: "@openclaw/legacy-doctor-index",
pluginId,
version: "1.0.0",
});
const packageJsonPath = path.join(pluginDir, "package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
openclaw: Record<string, unknown>;
};
fs.writeFileSync(
packageJsonPath,
JSON.stringify({
...packageJson,
openclaw: {
...packageJson.openclaw,
build: {
bundledDist: false,
openclawVersion: "2026.7.2",
pluginSdkVersion: "2026.7.2",
},
},
}),
"utf8",
);
fs.writeFileSync(
path.join(pluginDir, "doctor-contract-api.cjs"),
"module.exports = { stateMigrations: [] };\n",
"utf8",
);
const current = loadPluginMetadataSnapshot({
config,
env: instance.env,
stateDir: instance.stateDir,
});
const legacyIndex = {
...current.index,
plugins: current.index.plugins.map((plugin) => {
const {
doctorContractFile: _doctorContractFile,
doctorContractHash: _doctorContractHash,
...legacyPlugin
} = plugin;
return legacyPlugin;
}),
};
writePersistedInstalledPluginIndexSync(legacyIndex, { env: instance.env });
clearPluginMetadataLifecycleCaches();
closeOpenClawStateDatabaseForTest();
expect(await instance.entrypoint()).toEqual([
expect.stringMatching(/^dist\/index\.(?:js|mjs)$/u),
]);
await instance.startGateway();
expect(hasActiveStartupMigrationLease({ env: instance.env }), instance.logs()).toBe(false);
clearPluginMetadataLifecycleCaches();
closeOpenClawStateDatabaseForTest();
const reread = loadPluginMetadataSnapshot({
config,
env: instance.env,
stateDir: instance.stateDir,
allowCurrent: false,
});
expect(reread.registrySource, instance.logs()).toBe("persisted");
expect(reread.registryDiagnostics, instance.logs()).toStrictEqual([]);
const persisted = readPersistedInstalledPluginIndexSync({ env: instance.env });
const persistedPlugin = persisted?.plugins.find((plugin) => plugin.pluginId === pluginId);
expect(persistedPlugin, instance.logs()).toMatchObject({
doctorContractFile: {
ctimeMs: expect.any(Number),
mtimeMs: expect.any(Number),
size: expect.any(Number),
},
doctorContractHash: expect.stringMatching(/^[a-f0-9]{64}$/u),
packageBuild: { bundledDist: false },
});
}, 120_000);
});