|
| 1 | +import crypto from "node:crypto"; |
1 | 2 | import fs from "node:fs/promises"; |
2 | 3 | import os from "node:os"; |
3 | 4 | import path from "node:path"; |
@@ -109,3 +110,61 @@ describe("runPostUpgradeProbes — plugin.entry_unresolved", () => {
|
109 | 110 | } |
110 | 111 | }); |
111 | 112 | }); |
| 113 | + |
| 114 | +describe("runPostUpgradeProbes — plugin.manifest_drift", () => { |
| 115 | +it("flags a plugin whose manifest hash differs from installs.json", async () => { |
| 116 | +const root = await fs.mkdtemp(path.join(os.tmpdir(), "doctor-post-upgrade-manifest-drift-")); |
| 117 | +try { |
| 118 | +const pluginDir = path.join(root, "user-plugins", "drifted"); |
| 119 | +await fs.mkdir(path.join(pluginDir, "dist"), { recursive: true }); |
| 120 | +await fs.writeFile(path.join(pluginDir, "dist", "index.js"), "export default {};", "utf-8"); |
| 121 | +await fs.writeFile( |
| 122 | +path.join(pluginDir, "package.json"), |
| 123 | +JSON.stringify({ |
| 124 | +name: "drifted", |
| 125 | +version: "0.0.1", |
| 126 | +type: "module", |
| 127 | +openclaw: { extensions: ["./dist/index.js"] }, |
| 128 | +}), |
| 129 | +"utf-8", |
| 130 | +); |
| 131 | + |
| 132 | +const oldManifestRaw = JSON.stringify({ id: "drifted", version: 1 }); |
| 133 | +const oldManifestHash = crypto.createHash("sha256").update(oldManifestRaw).digest("hex"); |
| 134 | +// Write a NEW manifest after installs.json was snapshotted. |
| 135 | +await fs.writeFile( |
| 136 | +path.join(pluginDir, "openclaw.plugin.json"), |
| 137 | +JSON.stringify({ id: "drifted", version: 2 }), |
| 138 | +"utf-8", |
| 139 | +); |
| 140 | + |
| 141 | +const installsPath = path.join(root, "plugins", "installs.json"); |
| 142 | +await fs.mkdir(path.dirname(installsPath), { recursive: true }); |
| 143 | +await fs.writeFile( |
| 144 | +installsPath, |
| 145 | +JSON.stringify({ |
| 146 | +version: 1, |
| 147 | +plugins: [ |
| 148 | +{ |
| 149 | +pluginId: "drifted", |
| 150 | +manifestPath: path.join(pluginDir, "openclaw.plugin.json"), |
| 151 | +manifestHash: oldManifestHash, |
| 152 | +rootDir: pluginDir, |
| 153 | +enabled: true, |
| 154 | +packageJson: { path: "package.json" }, |
| 155 | +}, |
| 156 | +], |
| 157 | +}), |
| 158 | +"utf-8", |
| 159 | +); |
| 160 | + |
| 161 | +const report = await runPostUpgradeProbes({ installsPath }); |
| 162 | +const finding = report.findings.find((f) => f.code === "plugin.manifest_drift"); |
| 163 | +expect(finding).toBeDefined(); |
| 164 | +expect(finding?.level).toBe("warn"); |
| 165 | +expect(finding?.plugin).toBe("drifted"); |
| 166 | +} finally { |
| 167 | +await fs.rm(root, { recursive: true, force: true }); |
| 168 | +} |
| 169 | +}); |
| 170 | +}); |