mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-01 11:03:33 +00:00
321 lines
9.3 KiB
TypeScript
321 lines
9.3 KiB
TypeScript
/** Tests plugin version drift detection between package, manifest, and install records. */
|
|
import { describe, expect, it } from "vitest";
|
|
import type { OpenClawConfig } from "../config/types.js";
|
|
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
|
import { detectPluginVersionDrift } from "./plugin-version-drift.js";
|
|
|
|
function npmRecord(
|
|
version: string,
|
|
overrides: Partial<PluginInstallRecord> = {},
|
|
): PluginInstallRecord {
|
|
const resolvedName = overrides.resolvedName ?? "@openclaw/whatsapp";
|
|
return {
|
|
source: "npm",
|
|
spec: `${resolvedName}@latest`,
|
|
resolvedName,
|
|
resolvedVersion: version,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function clawhubRecord(
|
|
version: string,
|
|
overrides: Partial<PluginInstallRecord> = {},
|
|
): PluginInstallRecord {
|
|
return {
|
|
source: "clawhub",
|
|
spec: "clawhub:@openclaw/whatsapp",
|
|
clawhubPackage: "@openclaw/whatsapp",
|
|
resolvedVersion: version,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("detectPluginVersionDrift", () => {
|
|
it("returns empty drifts when all externalized plugins match the gateway", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: npmRecord("2026.5.4"),
|
|
discord: npmRecord("2026.5.4", { resolvedName: "@openclaw/discord" }),
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toEqual([]);
|
|
expect(result.gatewayVersion).toBe("2026.5.4");
|
|
});
|
|
|
|
it("reports plugins whose installed version does not match the gateway", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: npmRecord("2026.5.3", {
|
|
resolvedName: "@openclaw/whatsapp",
|
|
spec: "@openclaw/whatsapp@2026.5.3",
|
|
}),
|
|
discord: npmRecord("2026.5.4", { resolvedName: "@openclaw/discord" }),
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toHaveLength(1);
|
|
expect(result.drifts[0]).toEqual({
|
|
pluginId: "whatsapp",
|
|
installedVersion: "2026.5.3",
|
|
gatewayVersion: "2026.5.4",
|
|
source: "npm",
|
|
packageName: "@openclaw/whatsapp",
|
|
spec: "@openclaw/whatsapp@2026.5.3",
|
|
});
|
|
});
|
|
|
|
it("treats a build-qualifier suffix on either side as matching (2026.5.4-1 ≈ 2026.5.4)", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4-1",
|
|
installRecords: {
|
|
whatsapp: npmRecord("2026.5.4"),
|
|
// ...and the inverse direction
|
|
discord: npmRecord("2026.5.4-1", { resolvedName: "@openclaw/discord" }),
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toEqual([]);
|
|
});
|
|
|
|
it("includes ClawHub-installed plugins in the drift check", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: clawhubRecord("2026.5.3"),
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toHaveLength(1);
|
|
expect(result.drifts[0]?.source).toBe("clawhub");
|
|
});
|
|
|
|
it("includes official ClawHub installs whose catalog entry only declares npm install metadata", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
discord: clawhubRecord("2026.5.3", {
|
|
spec: "clawhub:@openclaw/discord",
|
|
clawhubPackage: "@openclaw/discord",
|
|
clawhubChannel: "official",
|
|
clawhubUrl: "https://clawhub.ai",
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.drifts.map((d) => d.pluginId)).toEqual(["discord"]);
|
|
});
|
|
|
|
it("ignores community npm installs without an official lockstep contract", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
community: npmRecord("1.2.3", {
|
|
resolvedName: "community-plugin",
|
|
spec: "community-plugin@1.2.3",
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toEqual([]);
|
|
});
|
|
|
|
it("ignores community ClawHub installs without an official lockstep contract", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
community: clawhubRecord("1.2.3", {
|
|
spec: "clawhub:community-plugin@1.2.3",
|
|
clawhubPackage: "community-plugin",
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toEqual([]);
|
|
});
|
|
|
|
it("ignores official catalog installs pinned to independent package versions", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
"openclaw-plugin-yuanbao": npmRecord("2.13.1", {
|
|
resolvedName: "openclaw-plugin-yuanbao",
|
|
spec: "openclaw-plugin-yuanbao@2.13.1",
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toEqual([]);
|
|
});
|
|
|
|
it("ignores exact catalog pins even when the pin matches the gateway version", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.7",
|
|
installRecords: {
|
|
"wecom-openclaw-plugin": npmRecord("2026.5.6", {
|
|
resolvedName: "@wecom/wecom-openclaw-plugin",
|
|
spec: "@wecom/wecom-openclaw-plugin@2026.5.6",
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toEqual([]);
|
|
});
|
|
|
|
it("ignores install sources that are not official external installs", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
// archive/path/git installs are local artifacts; they pin to whatever
|
|
// the operator chose and should not be flagged on a gateway version
|
|
// bump alone.
|
|
archive: {
|
|
source: "archive",
|
|
resolvedName: "@openclaw/whatsapp",
|
|
resolvedVersion: "2026.5.3",
|
|
spec: "@openclaw/whatsapp@archive",
|
|
},
|
|
local: {
|
|
source: "path",
|
|
resolvedName: "@openclaw/whatsapp",
|
|
resolvedVersion: "2026.5.3",
|
|
spec: "/tmp/local-plugin",
|
|
},
|
|
forked: {
|
|
source: "git",
|
|
resolvedName: "@openclaw/whatsapp",
|
|
resolvedVersion: "2026.5.3",
|
|
spec: "git+ssh://example/forked",
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toEqual([]);
|
|
});
|
|
|
|
it("falls back to the install record's `version` field when `resolvedVersion` is absent", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: {
|
|
source: "npm",
|
|
spec: "@openclaw/whatsapp@latest",
|
|
resolvedName: "@openclaw/whatsapp",
|
|
version: "2026.5.3",
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toHaveLength(1);
|
|
expect(result.drifts[0]?.installedVersion).toBe("2026.5.3");
|
|
});
|
|
|
|
it("skips plugins with no recorded version (cannot detect drift)", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: { source: "npm", spec: "@openclaw/whatsapp@latest" },
|
|
},
|
|
});
|
|
|
|
expect(result.drifts).toEqual([]);
|
|
});
|
|
|
|
it("skips plugins that are explicitly disabled in config", () => {
|
|
const config: OpenClawConfig = {
|
|
plugins: {
|
|
entries: {
|
|
whatsapp: { enabled: false },
|
|
discord: { enabled: true },
|
|
},
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: npmRecord("2026.5.3"),
|
|
discord: npmRecord("2026.5.3", { resolvedName: "@openclaw/discord" }),
|
|
},
|
|
config,
|
|
});
|
|
|
|
expect(result.drifts.map((d) => d.pluginId)).toEqual(["discord"]);
|
|
});
|
|
|
|
it("skips plugins disabled by the global plugin activation policy", () => {
|
|
const config: OpenClawConfig = {
|
|
plugins: {
|
|
enabled: false,
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: npmRecord("2026.5.3"),
|
|
},
|
|
config,
|
|
});
|
|
|
|
expect(result.drifts).toEqual([]);
|
|
});
|
|
|
|
it("skips plugins blocked by denylist or restrictive allowlist policy", () => {
|
|
const denied = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: npmRecord("2026.5.3"),
|
|
},
|
|
config: {
|
|
plugins: {
|
|
deny: ["whatsapp"],
|
|
},
|
|
} as OpenClawConfig,
|
|
});
|
|
const notAllowed = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: npmRecord("2026.5.3"),
|
|
},
|
|
config: {
|
|
plugins: {
|
|
allow: ["discord"],
|
|
},
|
|
} as OpenClawConfig,
|
|
});
|
|
|
|
expect(denied.drifts).toEqual([]);
|
|
expect(notAllowed.drifts).toEqual([]);
|
|
});
|
|
|
|
it("includes plugins with no entry in config (default-enabled)", () => {
|
|
const config: OpenClawConfig = { plugins: { entries: {} } } as OpenClawConfig;
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: npmRecord("2026.5.3"),
|
|
},
|
|
config,
|
|
});
|
|
|
|
expect(result.drifts).toHaveLength(1);
|
|
});
|
|
|
|
it("returns drifts sorted by pluginId for deterministic output", () => {
|
|
const result = detectPluginVersionDrift({
|
|
gatewayVersion: "2026.5.4",
|
|
installRecords: {
|
|
whatsapp: npmRecord("2026.5.3"),
|
|
discord: npmRecord("2026.5.3", { resolvedName: "@openclaw/discord" }),
|
|
matrix: npmRecord("2026.5.3", { resolvedName: "@openclaw/matrix" }),
|
|
},
|
|
});
|
|
|
|
expect(result.drifts.map((d) => d.pluginId)).toEqual(["discord", "matrix", "whatsapp"]);
|
|
});
|
|
});
|