186 lines
5.7 KiB
TypeScript
186 lines
5.7 KiB
TypeScript
import path from "node:path";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("node:fs", () => ({
|
|
default: {
|
|
existsSync: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
const installPluginFromNpmSpec = vi.fn();
|
|
vi.mock("../../plugins/install.js", () => ({
|
|
installPluginFromNpmSpec: (...args: unknown[]) => installPluginFromNpmSpec(...args),
|
|
}));
|
|
|
|
vi.mock("../../plugins/loader.js", () => ({
|
|
loadClawdbotPlugins: vi.fn(),
|
|
}));
|
|
|
|
import fs from "node:fs";
|
|
import type { ChannelPluginCatalogEntry } from "../../channels/plugins/catalog.js";
|
|
import type { ClawdbotConfig } from "../../config/config.js";
|
|
import type { WizardPrompter } from "../../wizard/prompts.js";
|
|
import { makePrompter, makeRuntime } from "./__tests__/test-utils.js";
|
|
import { ensureOnboardingPluginInstalled } from "./plugin-install.js";
|
|
|
|
const baseEntry: ChannelPluginCatalogEntry = {
|
|
id: "zalo",
|
|
meta: {
|
|
id: "zalo",
|
|
label: "Zalo",
|
|
selectionLabel: "Zalo (Bot API)",
|
|
docsPath: "/channels/zalo",
|
|
docsLabel: "zalo",
|
|
blurb: "Test",
|
|
},
|
|
install: {
|
|
npmSpec: "@clawdbot/zalo",
|
|
localPath: "extensions/zalo",
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("ensureOnboardingPluginInstalled", () => {
|
|
it("installs from npm and enables the plugin", async () => {
|
|
const runtime = makeRuntime();
|
|
const prompter = makePrompter({
|
|
select: vi.fn(async () => "npm") as WizardPrompter["select"],
|
|
});
|
|
const cfg: ClawdbotConfig = { plugins: { allow: ["other"] } };
|
|
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
installPluginFromNpmSpec.mockResolvedValue({
|
|
ok: true,
|
|
pluginId: "zalo",
|
|
targetDir: "/tmp/zalo",
|
|
extensions: [],
|
|
});
|
|
|
|
const result = await ensureOnboardingPluginInstalled({
|
|
cfg,
|
|
entry: baseEntry,
|
|
prompter,
|
|
runtime,
|
|
});
|
|
|
|
expect(result.installed).toBe(true);
|
|
expect(result.cfg.plugins?.entries?.zalo?.enabled).toBe(true);
|
|
expect(result.cfg.plugins?.allow).toContain("zalo");
|
|
expect(result.cfg.plugins?.installs?.zalo?.source).toBe("npm");
|
|
expect(result.cfg.plugins?.installs?.zalo?.spec).toBe("@clawdbot/zalo");
|
|
expect(result.cfg.plugins?.installs?.zalo?.installPath).toBe("/tmp/zalo");
|
|
expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
|
|
expect.objectContaining({ spec: "@clawdbot/zalo" }),
|
|
);
|
|
});
|
|
|
|
it("uses local path when selected", async () => {
|
|
const runtime = makeRuntime();
|
|
const prompter = makePrompter({
|
|
select: vi.fn(async () => "local") as WizardPrompter["select"],
|
|
});
|
|
const cfg: ClawdbotConfig = {};
|
|
vi.mocked(fs.existsSync).mockImplementation((value) => {
|
|
const raw = String(value);
|
|
return (
|
|
raw.endsWith(`${path.sep}.git`) || raw.endsWith(`${path.sep}extensions${path.sep}zalo`)
|
|
);
|
|
});
|
|
|
|
const result = await ensureOnboardingPluginInstalled({
|
|
cfg,
|
|
entry: baseEntry,
|
|
prompter,
|
|
runtime,
|
|
});
|
|
|
|
const expectedPath = path.resolve(process.cwd(), "extensions/zalo");
|
|
expect(result.installed).toBe(true);
|
|
expect(result.cfg.plugins?.load?.paths).toContain(expectedPath);
|
|
expect(result.cfg.plugins?.entries?.zalo?.enabled).toBe(true);
|
|
});
|
|
|
|
it("defaults to local on dev channel when local path exists", async () => {
|
|
const runtime = makeRuntime();
|
|
const select = vi.fn(async () => "skip") as WizardPrompter["select"];
|
|
const prompter = makePrompter({ select });
|
|
const cfg: ClawdbotConfig = { update: { channel: "dev" } };
|
|
vi.mocked(fs.existsSync).mockImplementation((value) => {
|
|
const raw = String(value);
|
|
return (
|
|
raw.endsWith(`${path.sep}.git`) || raw.endsWith(`${path.sep}extensions${path.sep}zalo`)
|
|
);
|
|
});
|
|
|
|
await ensureOnboardingPluginInstalled({
|
|
cfg,
|
|
entry: baseEntry,
|
|
prompter,
|
|
runtime,
|
|
});
|
|
|
|
const firstCall = select.mock.calls[0]?.[0];
|
|
expect(firstCall?.initialValue).toBe("local");
|
|
});
|
|
|
|
it("defaults to npm on beta channel even when local path exists", async () => {
|
|
const runtime = makeRuntime();
|
|
const select = vi.fn(async () => "skip") as WizardPrompter["select"];
|
|
const prompter = makePrompter({ select });
|
|
const cfg: ClawdbotConfig = { update: { channel: "beta" } };
|
|
vi.mocked(fs.existsSync).mockImplementation((value) => {
|
|
const raw = String(value);
|
|
return (
|
|
raw.endsWith(`${path.sep}.git`) || raw.endsWith(`${path.sep}extensions${path.sep}zalo`)
|
|
);
|
|
});
|
|
|
|
await ensureOnboardingPluginInstalled({
|
|
cfg,
|
|
entry: baseEntry,
|
|
prompter,
|
|
runtime,
|
|
});
|
|
|
|
const firstCall = select.mock.calls[0]?.[0];
|
|
expect(firstCall?.initialValue).toBe("npm");
|
|
});
|
|
|
|
it("falls back to local path after npm install failure", async () => {
|
|
const runtime = makeRuntime();
|
|
const note = vi.fn(async () => {});
|
|
const confirm = vi.fn(async () => true);
|
|
const prompter = makePrompter({
|
|
select: vi.fn(async () => "npm") as WizardPrompter["select"],
|
|
note,
|
|
confirm,
|
|
});
|
|
const cfg: ClawdbotConfig = {};
|
|
vi.mocked(fs.existsSync).mockImplementation((value) => {
|
|
const raw = String(value);
|
|
return (
|
|
raw.endsWith(`${path.sep}.git`) || raw.endsWith(`${path.sep}extensions${path.sep}zalo`)
|
|
);
|
|
});
|
|
installPluginFromNpmSpec.mockResolvedValue({
|
|
ok: false,
|
|
error: "nope",
|
|
});
|
|
|
|
const result = await ensureOnboardingPluginInstalled({
|
|
cfg,
|
|
entry: baseEntry,
|
|
prompter,
|
|
runtime,
|
|
});
|
|
|
|
const expectedPath = path.resolve(process.cwd(), "extensions/zalo");
|
|
expect(result.installed).toBe(true);
|
|
expect(result.cfg.plugins?.load?.paths).toContain(expectedPath);
|
|
expect(note).toHaveBeenCalled();
|
|
expect(runtime.error).not.toHaveBeenCalled();
|
|
});
|
|
});
|