
































@@ -641,6 +641,151 @@ describe("updateNpmInstalledPlugins", () => {
641641expect(installPluginFromNpmSpecMock).toHaveBeenCalledTimes(1);
642642});
643643644+it.each([
645+{
646+source: "npm",
647+config: {
648+plugins: {
649+entries: {
650+demo: {
651+enabled: false,
652+config: { preserved: true },
653+},
654+},
655+installs: {
656+demo: {
657+source: "npm" as const,
658+spec: "@acme/demo",
659+installPath: "/tmp/demo",
660+resolvedName: "@acme/demo",
661+},
662+},
663+},
664+} satisfies OpenClawConfig,
665+},
666+{
667+source: "ClawHub",
668+config: {
669+plugins: {
670+entries: {
671+demo: {
672+enabled: false,
673+config: { preserved: true },
674+},
675+},
676+installs: {
677+demo: {
678+source: "clawhub" as const,
679+spec: "clawhub:demo",
680+installPath: "/tmp/demo",
681+clawhubUrl: "https://clawhub.ai",
682+clawhubPackage: "demo",
683+clawhubFamily: "code-plugin",
684+clawhubChannel: "official",
685+},
686+},
687+},
688+} satisfies OpenClawConfig,
689+},
690+{
691+source: "marketplace",
692+config: {
693+plugins: {
694+entries: {
695+demo: {
696+enabled: false,
697+config: { preserved: true },
698+},
699+},
700+installs: {
701+demo: {
702+source: "marketplace" as const,
703+installPath: "/tmp/demo",
704+marketplaceSource: "acme/plugins",
705+marketplacePlugin: "demo",
706+},
707+},
708+},
709+} satisfies OpenClawConfig,
710+},
711+])("skips disabled $source installs before update network calls", async ({ config }) => {
712+installPluginFromNpmSpecMock.mockRejectedValue(new Error("npm installer should not run"));
713+installPluginFromClawHubMock.mockRejectedValue(new Error("ClawHub installer should not run"));
714+installPluginFromMarketplaceMock.mockRejectedValue(
715+new Error("marketplace installer should not run"),
716+);
717+718+const result = await updateNpmInstalledPlugins({
719+ config,
720+skipDisabledPlugins: true,
721+});
722+723+expect(runCommandWithTimeoutMock).not.toHaveBeenCalled();
724+expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
725+expect(installPluginFromClawHubMock).not.toHaveBeenCalled();
726+expect(installPluginFromMarketplaceMock).not.toHaveBeenCalled();
727+expect(result.changed).toBe(false);
728+expect(result.config).toBe(config);
729+expect(result.config.plugins?.installs?.demo).toEqual(config.plugins.installs.demo);
730+expect(result.config.plugins?.entries?.demo).toEqual({
731+enabled: false,
732+config: { preserved: true },
733+});
734+expect(result.outcomes).toEqual([
735+{
736+pluginId: "demo",
737+status: "skipped",
738+message: 'Skipping "demo" (disabled in config).',
739+},
740+]);
741+});
742+743+it("keeps enabled tracked plugin update failures fatal when disabled skipping is enabled", async () => {
744+installPluginFromNpmSpecMock.mockResolvedValue({
745+ok: false,
746+error: "registry timeout",
747+});
748+const config = {
749+plugins: {
750+entries: {
751+demo: {
752+enabled: true,
753+},
754+},
755+installs: {
756+demo: {
757+source: "npm" as const,
758+spec: "@acme/demo",
759+installPath: "/tmp/demo",
760+},
761+},
762+},
763+} satisfies OpenClawConfig;
764+765+const result = await updateNpmInstalledPlugins({
766+ config,
767+skipDisabledPlugins: true,
768+dryRun: true,
769+});
770+771+expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
772+expect.objectContaining({
773+spec: "@acme/demo",
774+expectedPluginId: "demo",
775+dryRun: true,
776+}),
777+);
778+expect(result.changed).toBe(false);
779+expect(result.config).toBe(config);
780+expect(result.outcomes).toEqual([
781+{
782+pluginId: "demo",
783+status: "error",
784+message: "Failed to check demo: registry timeout",
785+},
786+]);
787+});
788+644789it("aborts exact pinned npm plugin updates on integrity drift by default", async () => {
645790const warn = vi.fn();
646791installPluginFromNpmSpecMock.mockImplementation(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。