Files
openclaw/src/cli/plugins-update-selection.test.ts
T
Peter Steinberger c663c39763 refactor(test): table-drive exact-duplicate test blocks, batch 2 (#120071)
* refactor(test): table-drive tooling test cases

* refactor(test): table-drive agent test cases

* refactor(test): table-drive core test cases

* refactor(test): table-drive plugin test cases

* refactor(test): table-drive package test cases

* chore(test): prune max-lines baseline

* fix(test): preserve table row literal types
2026-08-06 17:00:28 -07:00

198 lines
6.0 KiB
TypeScript

// Plugin update selection tests cover CLI plugin update target selection.
import { describe, expect, it } from "vitest";
import type { HookInstallRecord } from "../config/types.hooks.js";
import type { PluginInstallRecord } from "../config/types.plugins.js";
import {
resolveHookPackUpdateSelection,
resolvePluginUpdateSelection,
} from "./plugins-update-selection.js";
function createNpmInstall(params: {
spec: string;
installPath?: string;
resolvedName?: string;
}): PluginInstallRecord {
return {
source: "npm",
spec: params.spec,
installPath: params.installPath ?? "/tmp/plugin",
...(params.resolvedName ? { resolvedName: params.resolvedName } : {}),
};
}
function createNpmHookInstall(params: {
spec: string;
installPath?: string;
resolvedName?: string;
}): HookInstallRecord {
return {
source: "npm",
spec: params.spec,
installPath: params.installPath ?? "/tmp/hook-pack",
...(params.resolvedName ? { resolvedName: params.resolvedName } : {}),
};
}
describe("resolvePluginUpdateSelection", () => {
it.each(["missing-plugin", "@acme/missing-plugin@beta", "constructor"])(
"does not select the untracked plugin target %s",
(rawId) => {
expect(resolvePluginUpdateSelection({ installs: {}, rawId })).toEqual({ pluginIds: [] });
},
);
it("does not guess an owner when an npm package maps to multiple tracked plugins", () => {
expect(
resolvePluginUpdateSelection({
installs: {
alpha: createNpmInstall({ spec: "@acme/shared", resolvedName: "@acme/shared" }),
beta: createNpmInstall({ spec: "@acme/shared", resolvedName: "@acme/shared" }),
},
rawId: "@acme/shared@beta",
}),
).toEqual({ pluginIds: [] });
});
it.each([
{
title: "maps an explicit unscoped npm dist-tag update to the tracked plugin id",
pluginId: "openclaw-codex-app-server",
packageNameWithSpec: "openclaw-codex-app-server",
installPath: "/tmp/openclaw-codex-app-server",
packageName: "openclaw-codex-app-server",
requestedSpec: "openclaw-codex-app-server@beta",
expectedPluginId: "openclaw-codex-app-server",
expectedTrackedId: "openclaw-codex-app-server",
expectedSpec: "openclaw-codex-app-server@beta",
},
{
title: "maps an explicit scoped npm dist-tag update to the tracked plugin id",
pluginId: "voice-call",
packageNameWithSpec: "@openclaw/voice-call",
installPath: "/tmp/voice-call",
packageName: "@openclaw/voice-call",
requestedSpec: "@openclaw/voice-call@beta",
expectedPluginId: "voice-call",
expectedTrackedId: "voice-call",
expectedSpec: "@openclaw/voice-call@beta",
},
{
title: "maps an explicit npm version update to the tracked plugin id",
pluginId: "openclaw-codex-app-server",
packageNameWithSpec: "openclaw-codex-app-server",
installPath: "/tmp/openclaw-codex-app-server",
packageName: "openclaw-codex-app-server",
requestedSpec: "openclaw-codex-app-server@0.2.0-beta.4",
expectedPluginId: "openclaw-codex-app-server",
expectedTrackedId: "openclaw-codex-app-server",
expectedSpec: "openclaw-codex-app-server@0.2.0-beta.4",
},
{
title: "maps a bare scoped npm package update to the tracked plugin id",
pluginId: "lossless-claw",
packageNameWithSpec: "@martian-engineering/lossless-claw@0.9.0",
installPath: "/tmp/lossless-claw",
packageName: "@martian-engineering/lossless-claw",
requestedSpec: "@martian-engineering/lossless-claw",
expectedPluginId: "lossless-claw",
expectedTrackedId: "lossless-claw",
expectedSpec: "@martian-engineering/lossless-claw",
},
])(
"$title",
({
pluginId,
packageNameWithSpec,
installPath,
packageName,
requestedSpec,
expectedPluginId,
expectedTrackedId,
expectedSpec,
}) => {
expect(
resolvePluginUpdateSelection({
installs: {
[pluginId]: createNpmInstall({
spec: packageNameWithSpec,
installPath,
resolvedName: packageName,
}),
},
rawId: requestedSpec,
}),
).toEqual({
pluginIds: [expectedPluginId],
specOverrides: {
[expectedTrackedId]: expectedSpec,
},
});
},
);
it("keeps recorded npm tags when update is invoked by plugin id", () => {
expect(
resolvePluginUpdateSelection({
installs: {
"openclaw-codex-app-server": createNpmInstall({
spec: "openclaw-codex-app-server@beta",
installPath: "/tmp/openclaw-codex-app-server",
resolvedName: "openclaw-codex-app-server",
}),
},
rawId: "openclaw-codex-app-server",
}),
).toEqual({
pluginIds: ["openclaw-codex-app-server"],
});
});
it("maps prototype-named npm packages by own install records", () => {
expect(
resolvePluginUpdateSelection({
installs: {
"tracked-constructor": createNpmInstall({
spec: "constructor",
resolvedName: "constructor",
}),
},
rawId: "constructor",
}),
).toEqual({
pluginIds: ["tracked-constructor"],
specOverrides: {
"tracked-constructor": "constructor",
},
});
});
});
describe("resolveHookPackUpdateSelection", () => {
it("does not treat inherited prototype keys as installed hook ids", () => {
expect(
resolveHookPackUpdateSelection({
installs: {},
rawId: "constructor",
}),
).toEqual({
hookIds: [],
});
});
it("keeps own prototype-named hook ids selectable", () => {
expect(
resolveHookPackUpdateSelection({
installs: {
constructor: createNpmHookInstall({
spec: "openclaw-hooks-constructor",
resolvedName: "openclaw-hooks-constructor",
}),
},
rawId: "constructor",
}),
).toEqual({
hookIds: ["constructor"],
});
});
});