|
1 | 1 | import path from "node:path"; |
2 | 2 | import { beforeEach, describe, expect, it, vi } from "vitest"; |
3 | 3 |
|
4 | | -vi.mock("node:fs", () => ({ |
5 | | - default: { |
6 | | - existsSync: vi.fn(), |
7 | | - }, |
8 | | -})); |
| 4 | +vi.mock("node:fs", async (importOriginal) => { |
| 5 | + const actual = await importOriginal<typeof import("node:fs")>(); |
| 6 | + const existsSync = vi.fn(); |
| 7 | + return { |
| 8 | + ...actual, |
| 9 | + existsSync, |
| 10 | + default: { |
| 11 | + ...actual, |
| 12 | + existsSync, |
| 13 | + }, |
| 14 | + }; |
| 15 | +}); |
9 | 16 |
|
10 | 17 | const installPluginFromNpmSpec = vi.fn(); |
11 | 18 | vi.mock("../../plugins/install.js", () => ({ |
12 | 19 | installPluginFromNpmSpec: (...args: unknown[]) => installPluginFromNpmSpec(...args), |
13 | 20 | })); |
14 | 21 |
|
| 22 | +const resolveBundledPluginSources = vi.fn(); |
| 23 | +vi.mock("../../plugins/bundled-sources.js", () => ({ |
| 24 | + findBundledPluginSourceInMap: ({ |
| 25 | + bundled, |
| 26 | + lookup, |
| 27 | + }: { |
| 28 | + bundled: ReadonlyMap<string, { pluginId: string; localPath: string; npmSpec?: string }>; |
| 29 | + lookup: { kind: "pluginId" | "npmSpec"; value: string }; |
| 30 | + }) => { |
| 31 | + const targetValue = lookup.value.trim(); |
| 32 | + if (!targetValue) { |
| 33 | + return undefined; |
| 34 | + } |
| 35 | + if (lookup.kind === "pluginId") { |
| 36 | + return bundled.get(targetValue); |
| 37 | + } |
| 38 | + for (const source of bundled.values()) { |
| 39 | + if (source.npmSpec === targetValue) { |
| 40 | + return source; |
| 41 | + } |
| 42 | + } |
| 43 | + return undefined; |
| 44 | + }, |
| 45 | + resolveBundledPluginSources: (...args: unknown[]) => resolveBundledPluginSources(...args), |
| 46 | +})); |
| 47 | + |
15 | 48 | vi.mock("../../plugins/loader.js", () => ({ |
16 | 49 | loadOpenClawPlugins: vi.fn(), |
17 | 50 | })); |
@@ -41,6 +74,7 @@ const baseEntry: ChannelPluginCatalogEntry = { |
41 | 74 |
|
42 | 75 | beforeEach(() => { |
43 | 76 | vi.clearAllMocks(); |
| 77 | + resolveBundledPluginSources.mockReturnValue(new Map()); |
44 | 78 | }); |
45 | 79 |
|
46 | 80 | function mockRepoLocalPathExists() { |
@@ -136,6 +170,45 @@ describe("ensureOnboardingPluginInstalled", () => { |
136 | 170 | expect(await runInitialValueForChannel("beta")).toBe("npm"); |
137 | 171 | }); |
138 | 172 |
|
| 173 | + it("defaults to bundled local path on beta channel when available", async () => { |
| 174 | + const runtime = makeRuntime(); |
| 175 | + const select = vi.fn((async <T extends string>() => "skip" as T) as WizardPrompter["select"]); |
| 176 | + const prompter = makePrompter({ select: select as unknown as WizardPrompter["select"] }); |
| 177 | + const cfg: OpenClawConfig = { update: { channel: "beta" } }; |
| 178 | + vi.mocked(fs.existsSync).mockReturnValue(false); |
| 179 | + resolveBundledPluginSources.mockReturnValue( |
| 180 | + new Map([ |
| 181 | + [ |
| 182 | + "zalo", |
| 183 | + { |
| 184 | + pluginId: "zalo", |
| 185 | + localPath: "/opt/openclaw/extensions/zalo", |
| 186 | + npmSpec: "@openclaw/zalo", |
| 187 | + }, |
| 188 | + ], |
| 189 | + ]), |
| 190 | + ); |
| 191 | + |
| 192 | + await ensureOnboardingPluginInstalled({ |
| 193 | + cfg, |
| 194 | + entry: baseEntry, |
| 195 | + prompter, |
| 196 | + runtime, |
| 197 | + }); |
| 198 | + |
| 199 | + expect(select).toHaveBeenCalledWith( |
| 200 | + expect.objectContaining({ |
| 201 | + initialValue: "local", |
| 202 | + options: expect.arrayContaining([ |
| 203 | + expect.objectContaining({ |
| 204 | + value: "local", |
| 205 | + hint: "/opt/openclaw/extensions/zalo", |
| 206 | + }), |
| 207 | + ]), |
| 208 | + }), |
| 209 | + ); |
| 210 | + }); |
| 211 | + |
139 | 212 | it("falls back to local path after npm install failure", async () => { |
140 | 213 | const runtime = makeRuntime(); |
141 | 214 | const note = vi.fn(async () => {}); |
|
0 commit comments