openhands / src /api /plugins-management-service.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
63522a5 verified
Raw
History Blame Contribute Delete
4.16 kB
import { PluginsClient } from "@openhands/typescript-client/clients";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
__resetActiveStoreForTests,
setActiveSelection,
setRegisteredBackends,
} from "#/api/backend-registry/active-store";
import type { Backend } from "#/api/backend-registry/types";
import PluginsManagementService from "#/api/plugins-management-service";
vi.mock("@openhands/typescript-client/clients", () => ({
PluginsClient: vi.fn(),
}));
const listInstalledPlugins = vi.fn();
const installPlugin = vi.fn();
const setPluginEnabled = vi.fn();
const uninstallPlugin = vi.fn();
const refreshPlugin = vi.fn();
const close = vi.fn();
function useBackend(kind: "local" | "cloud"): void {
const backend: Backend = {
id: kind,
name: kind,
host: "http://127.0.0.1:8001",
apiKey: "session-key",
kind,
};
setRegisteredBackends([backend]);
setActiveSelection({ backendId: kind, orgId: null });
}
beforeEach(() => {
vi.clearAllMocks();
__resetActiveStoreForTests();
vi.mocked(PluginsClient).mockImplementation(function MockPluginsClient() {
return {
listInstalledPlugins,
installPlugin,
setPluginEnabled,
uninstallPlugin,
refreshPlugin,
close,
} as unknown as PluginsClient;
} as unknown as typeof PluginsClient);
});
afterEach(() => {
__resetActiveStoreForTests();
});
const installedPlugin = {
name: "demo-plugin",
version: "1.0.0",
description: "A demo plugin",
enabled: true,
source: "github:OpenHands/extensions",
resolved_ref: null,
repo_path: "plugins/demo-plugin",
installed_at: "2026-06-01T00:00:00Z",
install_path: "/home/.openhands/plugins/installed/demo-plugin",
};
describe("PluginsManagementService", () => {
it("lists installed plugins from the local agent-server", async () => {
useBackend("local");
listInstalledPlugins.mockResolvedValue({ plugins: [installedPlugin] });
const result = await PluginsManagementService.listInstalledPlugins();
expect(result).toEqual([installedPlugin]);
expect(listInstalledPlugins).toHaveBeenCalledTimes(1);
});
it("returns an empty installed list on a cloud backend without calling the client", async () => {
useBackend("cloud");
const result = await PluginsManagementService.listInstalledPlugins();
expect(result).toEqual([]);
expect(PluginsClient).not.toHaveBeenCalled();
});
it("returns an empty installed list when the local request fails", async () => {
useBackend("local");
listInstalledPlugins.mockRejectedValue(new Error("unreachable"));
const result = await PluginsManagementService.listInstalledPlugins();
expect(result).toEqual([]);
});
it("forwards source, ref, and repo_path when installing a plugin", async () => {
useBackend("local");
installPlugin.mockResolvedValue(installedPlugin);
await PluginsManagementService.installPlugin({
source: "github:OpenHands/extensions",
ref: "main",
repo_path: "plugins/demo-plugin",
});
expect(installPlugin).toHaveBeenCalledWith({
source: "github:OpenHands/extensions",
ref: "main",
repo_path: "plugins/demo-plugin",
});
});
it("forwards the name and enabled flag when toggling a plugin", async () => {
useBackend("local");
setPluginEnabled.mockResolvedValue({ name: "demo-plugin", enabled: false });
await PluginsManagementService.setPluginEnabled("demo-plugin", false);
expect(setPluginEnabled).toHaveBeenCalledWith("demo-plugin", false);
});
it("forwards the plugin name when uninstalling", async () => {
useBackend("local");
uninstallPlugin.mockResolvedValue({ message: "ok" });
await PluginsManagementService.uninstallPlugin("demo-plugin");
expect(uninstallPlugin).toHaveBeenCalledWith("demo-plugin");
});
it("forwards the plugin name when refreshing", async () => {
useBackend("local");
refreshPlugin.mockResolvedValue({ message: "ok", plugin: installedPlugin });
await PluginsManagementService.refreshPlugin("demo-plugin");
expect(refreshPlugin).toHaveBeenCalledWith("demo-plugin");
});
});