Skip to content

Commit 3a60464

Browse files
committed
test: simplify doctor migration fixtures
1 parent a132fa5 commit 3a60464

2 files changed

Lines changed: 142 additions & 218 deletions

File tree

src/commands/doctor-state-migrations.test.ts

Lines changed: 118 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
readPersistedInstalledPluginIndex,
1616
writePersistedInstalledPluginIndex,
1717
} from "../plugins/installed-plugin-index-store.js";
18+
import type { InstalledPluginInstallRecordInfo } from "../plugins/installed-plugin-index.js";
1819
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
1920
import { loadTaskFlowRegistryStateFromSqlite } from "../tasks/task-flow-registry.store.sqlite.js";
2021
import { loadTaskRegistryStateFromSqlite } from "../tasks/task-registry.store.sqlite.js";
@@ -299,6 +300,44 @@ function writeLegacyPluginStateSidecar(root: string): string {
299300
return sourcePath;
300301
}
301302

303+
async function writeExistingPluginInstallIndex(
304+
root: string,
305+
installRecords: Record<string, InstalledPluginInstallRecordInfo>,
306+
): Promise<void> {
307+
await writePersistedInstalledPluginIndex(
308+
{
309+
version: 1,
310+
hostContractVersion: "test",
311+
compatRegistryVersion: "test",
312+
migrationVersion: 1,
313+
policyHash: "test",
314+
generatedAtMs: 1,
315+
installRecords,
316+
plugins: [],
317+
diagnostics: [],
318+
},
319+
{ stateDir: root },
320+
);
321+
}
322+
323+
function writeLegacyPluginInstallIndex(
324+
root: string,
325+
records: Record<string, InstalledPluginInstallRecordInfo>,
326+
): string {
327+
const sourcePath = path.join(root, "plugins", "installs.json");
328+
fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
329+
fs.writeFileSync(sourcePath, JSON.stringify({ records }), "utf8");
330+
return sourcePath;
331+
}
332+
333+
async function runLegacyStateMigrationsForRoot(root: string) {
334+
const detected = await detectLegacyStateMigrations({
335+
cfg: {},
336+
env: { OPENCLAW_STATE_DIR: root } as NodeJS.ProcessEnv,
337+
});
338+
return await runLegacyStateMigrations({ detected });
339+
}
340+
302341
function writeLegacyTaskStateSidecars(root: string): {
303342
taskRunsPath: string;
304343
flowRunsPath: string;
@@ -1302,45 +1341,20 @@ describe("doctor legacy state migrations", () => {
13021341

13031342
it("merges missing legacy plugin install records into an existing SQLite index", async () => {
13041343
const root = await makeTempRoot();
1305-
await writePersistedInstalledPluginIndex(
1306-
{
1307-
version: 1,
1308-
hostContractVersion: "test",
1309-
compatRegistryVersion: "test",
1310-
migrationVersion: 1,
1311-
policyHash: "test",
1312-
generatedAtMs: 1,
1313-
installRecords: {
1314-
existing: {
1315-
source: "npm",
1316-
spec: "existing@1.0.0",
1317-
},
1318-
},
1319-
plugins: [],
1320-
diagnostics: [],
1344+
await writeExistingPluginInstallIndex(root, {
1345+
existing: {
1346+
source: "npm",
1347+
spec: "existing@1.0.0",
1348+
},
1349+
});
1350+
const sourcePath = writeLegacyPluginInstallIndex(root, {
1351+
legacy: {
1352+
source: "git",
1353+
spec: "git:file:///tmp/legacy",
13211354
},
1322-
{ stateDir: root },
1323-
);
1324-
const sourcePath = path.join(root, "plugins", "installs.json");
1325-
fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
1326-
fs.writeFileSync(
1327-
sourcePath,
1328-
JSON.stringify({
1329-
records: {
1330-
legacy: {
1331-
source: "git",
1332-
spec: "git:file:///tmp/legacy",
1333-
},
1334-
},
1335-
}),
1336-
"utf8",
1337-
);
1338-
1339-
const detected = await detectLegacyStateMigrations({
1340-
cfg: {},
1341-
env: { OPENCLAW_STATE_DIR: root } as NodeJS.ProcessEnv,
13421355
});
1343-
const result = await runLegacyStateMigrations({ detected });
1356+
1357+
const result = await runLegacyStateMigrationsForRoot(root);
13441358

13451359
expect(result.warnings).toStrictEqual([]);
13461360
expect(result.changes).toContain("Merged 1 legacy plugin install record → shared SQLite state");
@@ -1355,53 +1369,28 @@ describe("doctor legacy state migrations", () => {
13551369

13561370
it("archives legacy plugin install index when SQLite already has richer matching records", async () => {
13571371
const root = await makeTempRoot();
1358-
await writePersistedInstalledPluginIndex(
1359-
{
1360-
version: 1,
1361-
hostContractVersion: "test",
1362-
compatRegistryVersion: "test",
1363-
migrationVersion: 1,
1364-
policyHash: "test",
1365-
generatedAtMs: 1,
1366-
installRecords: {
1367-
demo: {
1368-
source: "npm",
1369-
spec: "demo@1.0.0",
1370-
version: "1.0.0",
1371-
resolvedName: "demo",
1372-
resolvedVersion: "1.0.0",
1373-
resolvedSpec: "demo@1.0.0",
1374-
integrity: "sha512-current",
1375-
shasum: "current",
1376-
installedAt: "2026-06-01T21:04:35.000Z",
1377-
},
1378-
},
1379-
plugins: [],
1380-
diagnostics: [],
1372+
await writeExistingPluginInstallIndex(root, {
1373+
demo: {
1374+
source: "npm",
1375+
spec: "demo@1.0.0",
1376+
version: "1.0.0",
1377+
resolvedName: "demo",
1378+
resolvedVersion: "1.0.0",
1379+
resolvedSpec: "demo@1.0.0",
1380+
integrity: "sha512-current",
1381+
shasum: "current",
1382+
installedAt: "2026-06-01T21:04:35.000Z",
13811383
},
1382-
{ stateDir: root },
1383-
);
1384-
const sourcePath = path.join(root, "plugins", "installs.json");
1385-
fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
1386-
fs.writeFileSync(
1387-
sourcePath,
1388-
JSON.stringify({
1389-
records: {
1390-
demo: {
1391-
source: "npm",
1392-
spec: "demo@beta",
1393-
version: "1.0.0",
1394-
},
1395-
},
1396-
}),
1397-
"utf8",
1398-
);
1399-
1400-
const detected = await detectLegacyStateMigrations({
1401-
cfg: {},
1402-
env: { OPENCLAW_STATE_DIR: root } as NodeJS.ProcessEnv,
14031384
});
1404-
const result = await runLegacyStateMigrations({ detected });
1385+
const sourcePath = writeLegacyPluginInstallIndex(root, {
1386+
demo: {
1387+
source: "npm",
1388+
spec: "demo@beta",
1389+
version: "1.0.0",
1390+
},
1391+
});
1392+
1393+
const result = await runLegacyStateMigrationsForRoot(root);
14051394

14061395
expect(result.warnings).toStrictEqual([]);
14071396
expect(fs.existsSync(sourcePath)).toBe(false);
@@ -1418,111 +1407,56 @@ describe("doctor legacy state migrations", () => {
14181407
});
14191408
});
14201409

1421-
it("keeps legacy plugin install index when same-version npm records name different packages", async () => {
1422-
const root = await makeTempRoot();
1423-
await writePersistedInstalledPluginIndex(
1424-
{
1425-
version: 1,
1426-
hostContractVersion: "test",
1427-
compatRegistryVersion: "test",
1428-
migrationVersion: 1,
1429-
policyHash: "test",
1430-
generatedAtMs: 1,
1431-
installRecords: {
1432-
demo: {
1433-
source: "npm",
1434-
spec: "@openclaw/demo@1.0.0",
1435-
version: "1.0.0",
1436-
resolvedName: "@openclaw/demo",
1437-
resolvedVersion: "1.0.0",
1438-
resolvedSpec: "@openclaw/demo@1.0.0",
1439-
},
1440-
},
1441-
plugins: [],
1442-
diagnostics: [],
1410+
for (const fixture of [
1411+
{
1412+
label: "name different packages",
1413+
current: {
1414+
source: "npm",
1415+
spec: "@openclaw/demo@1.0.0",
1416+
version: "1.0.0",
1417+
resolvedName: "@openclaw/demo",
1418+
resolvedVersion: "1.0.0",
1419+
resolvedSpec: "@openclaw/demo@1.0.0",
14431420
},
1444-
{ stateDir: root },
1445-
);
1446-
const sourcePath = path.join(root, "plugins", "installs.json");
1447-
fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
1448-
fs.writeFileSync(
1449-
sourcePath,
1450-
JSON.stringify({
1451-
records: {
1452-
demo: {
1453-
source: "npm",
1454-
spec: "@vendor/demo@1.0.0",
1455-
version: "1.0.0",
1456-
},
1457-
},
1458-
}),
1459-
"utf8",
1460-
);
1461-
1462-
const detected = await detectLegacyStateMigrations({
1463-
cfg: {},
1464-
env: { OPENCLAW_STATE_DIR: root } as NodeJS.ProcessEnv,
1465-
});
1466-
const result = await runLegacyStateMigrations({ detected });
1467-
1468-
expect(result.warnings).toStrictEqual([
1469-
"Left plugin install index in place because shared SQLite state has conflicting plugin install metadata for: demo",
1470-
]);
1471-
expect(fs.existsSync(sourcePath)).toBe(true);
1472-
expect(fs.existsSync(`${sourcePath}.migrated`)).toBe(false);
1473-
});
1474-
1475-
it("keeps legacy plugin install index when same-version npm specs are unparseable", async () => {
1476-
const root = await makeTempRoot();
1477-
await writePersistedInstalledPluginIndex(
1478-
{
1479-
version: 1,
1480-
hostContractVersion: "test",
1481-
compatRegistryVersion: "test",
1482-
migrationVersion: 1,
1483-
policyHash: "test",
1484-
generatedAtMs: 1,
1485-
installRecords: {
1486-
demo: {
1487-
source: "npm",
1488-
spec: "file:../current-demo",
1489-
version: "1.0.0",
1490-
resolvedVersion: "1.0.0",
1491-
},
1492-
},
1493-
plugins: [],
1494-
diagnostics: [],
1421+
legacy: {
1422+
source: "npm",
1423+
spec: "@vendor/demo@1.0.0",
1424+
version: "1.0.0",
14951425
},
1496-
{ stateDir: root },
1497-
);
1498-
const sourcePath = path.join(root, "plugins", "installs.json");
1499-
fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
1500-
fs.writeFileSync(
1501-
sourcePath,
1502-
JSON.stringify({
1503-
records: {
1504-
demo: {
1505-
source: "npm",
1506-
spec: "file:../legacy-demo",
1507-
version: "1.0.0",
1508-
},
1509-
},
1510-
}),
1511-
"utf8",
1512-
);
1513-
1514-
const detected = await detectLegacyStateMigrations({
1515-
cfg: {},
1516-
env: { OPENCLAW_STATE_DIR: root } as NodeJS.ProcessEnv,
1426+
},
1427+
{
1428+
label: "specs are unparseable",
1429+
current: {
1430+
source: "npm",
1431+
spec: "file:../current-demo",
1432+
version: "1.0.0",
1433+
resolvedVersion: "1.0.0",
1434+
},
1435+
legacy: {
1436+
source: "npm",
1437+
spec: "file:../legacy-demo",
1438+
version: "1.0.0",
1439+
},
1440+
},
1441+
] satisfies Array<{
1442+
label: string;
1443+
current: InstalledPluginInstallRecordInfo;
1444+
legacy: InstalledPluginInstallRecordInfo;
1445+
}>) {
1446+
it(`keeps legacy plugin install index when same-version npm records ${fixture.label}`, async () => {
1447+
const root = await makeTempRoot();
1448+
await writeExistingPluginInstallIndex(root, { demo: fixture.current });
1449+
const sourcePath = writeLegacyPluginInstallIndex(root, { demo: fixture.legacy });
1450+
1451+
const result = await runLegacyStateMigrationsForRoot(root);
1452+
1453+
expect(result.warnings).toStrictEqual([
1454+
"Left plugin install index in place because shared SQLite state has conflicting plugin install metadata for: demo",
1455+
]);
1456+
expect(fs.existsSync(sourcePath)).toBe(true);
1457+
expect(fs.existsSync(`${sourcePath}.migrated`)).toBe(false);
15171458
});
1518-
const result = await runLegacyStateMigrations({ detected });
1519-
1520-
expect(result.warnings).toStrictEqual([
1521-
"Left plugin install index in place because shared SQLite state has conflicting plugin install metadata for: demo",
1522-
]);
1523-
expect(fs.existsSync(sourcePath)).toBe(true);
1524-
expect(fs.existsSync(`${sourcePath}.migrated`)).toBe(false);
1525-
});
1459+
}
15261460

15271461
it("auto-migrates the shipped plugin-state SQLite sidecar by itself", async () => {
15281462
const root = await makeTempRoot();

0 commit comments

Comments
 (0)