diff --git a/apps/loopover-miner-ui/src/vite-chat-discover-attempt-actions.test.ts b/apps/loopover-miner-ui/src/vite-chat-discover-attempt-actions.test.ts new file mode 100644 index 0000000000..bf51148504 --- /dev/null +++ b/apps/loopover-miner-ui/src/vite-chat-discover-attempt-actions.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it, vi } from "vitest"; + +// The plugin dynamically imports this module by the same relative path it resolves to from here +// (apps/loopover-miner-ui/vite-chat-discover-attempt-actions.ts's `./src/lib/chat-discover-attempt-actions` +// and this test's `./lib/chat-discover-attempt-actions` both resolve to src/lib/chat-discover-attempt-actions.ts) +// — mocking it here intercepts the plugin's own dynamic import. +const h = vi.hoisted(() => ({ registerDiscoverAttemptChatActions: vi.fn() })); +vi.mock("./lib/chat-discover-attempt-actions", () => ({ + registerDiscoverAttemptChatActions: h.registerDiscoverAttemptChatActions, +})); + +import { chatDiscoverAttemptActionsPlugin } from "../vite-chat-discover-attempt-actions"; + +// #7228: `vite preview` (the systemd-deployed persistent-service path per the README) only ever invokes +// configurePreviewServer, never configureServer — proves the registration call also fires from that hook. +describe("chatDiscoverAttemptActionsPlugin (#6837, #7228)", () => { + it("registers a configureServer and a configurePreviewServer hook", () => { + const plugin = chatDiscoverAttemptActionsPlugin(); + expect(typeof plugin.configureServer).toBe("function"); + expect(typeof plugin.configurePreviewServer).toBe("function"); + }); + + it("invokes registerDiscoverAttemptChatActions when only configurePreviewServer is exercised", async () => { + h.registerDiscoverAttemptChatActions.mockClear(); + const plugin = chatDiscoverAttemptActionsPlugin(); + // @ts-expect-error -- configurePreviewServer's registration call reads no properties off its server arg. + plugin.configurePreviewServer?.(); + await vi.waitFor(() => expect(h.registerDiscoverAttemptChatActions).toHaveBeenCalledTimes(1)); + }); + + it("invokes registerDiscoverAttemptChatActions when configureServer is exercised (unchanged behavior)", async () => { + h.registerDiscoverAttemptChatActions.mockClear(); + const plugin = chatDiscoverAttemptActionsPlugin(); + // @ts-expect-error -- configureServer's registration call reads no properties off its server arg. + plugin.configureServer?.(); + await vi.waitFor(() => expect(h.registerDiscoverAttemptChatActions).toHaveBeenCalledTimes(1)); + }); +}); diff --git a/apps/loopover-miner-ui/src/vite-chat-governor-actions.test.ts b/apps/loopover-miner-ui/src/vite-chat-governor-actions.test.ts new file mode 100644 index 0000000000..d7328d721b --- /dev/null +++ b/apps/loopover-miner-ui/src/vite-chat-governor-actions.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it, vi } from "vitest"; + +// The plugin dynamically imports this module by the same relative path it resolves to from here +// (apps/loopover-miner-ui/vite-chat-governor-actions.ts's `./src/lib/chat-governor-actions` and this test's +// `./lib/chat-governor-actions` both resolve to src/lib/chat-governor-actions.ts) — mocking it here intercepts +// the plugin's own dynamic import. +const h = vi.hoisted(() => ({ registerGovernorChatActions: vi.fn() })); +vi.mock("./lib/chat-governor-actions", () => ({ registerGovernorChatActions: h.registerGovernorChatActions })); + +import { chatGovernorActionsPlugin } from "../vite-chat-governor-actions"; + +// #7228: `vite preview` (the systemd-deployed persistent-service path per the README) only ever invokes +// configurePreviewServer, never configureServer — proves the registration call also fires from that hook. +describe("chatGovernorActionsPlugin (#6521, #7228)", () => { + it("registers a configureServer and a configurePreviewServer hook", () => { + const plugin = chatGovernorActionsPlugin(); + expect(typeof plugin.configureServer).toBe("function"); + expect(typeof plugin.configurePreviewServer).toBe("function"); + }); + + it("invokes registerGovernorChatActions when only configurePreviewServer is exercised", async () => { + h.registerGovernorChatActions.mockClear(); + const plugin = chatGovernorActionsPlugin(); + // @ts-expect-error -- configurePreviewServer's registration call reads no properties off its server arg. + plugin.configurePreviewServer?.(); + await vi.waitFor(() => expect(h.registerGovernorChatActions).toHaveBeenCalledTimes(1)); + }); + + it("invokes registerGovernorChatActions when configureServer is exercised (unchanged behavior)", async () => { + h.registerGovernorChatActions.mockClear(); + const plugin = chatGovernorActionsPlugin(); + // @ts-expect-error -- configureServer's registration call reads no properties off its server arg. + plugin.configureServer?.(); + await vi.waitFor(() => expect(h.registerGovernorChatActions).toHaveBeenCalledTimes(1)); + }); +}); diff --git a/apps/loopover-miner-ui/vite-chat-discover-attempt-actions.ts b/apps/loopover-miner-ui/vite-chat-discover-attempt-actions.ts index a9f3a08f7e..26e8000f1a 100644 --- a/apps/loopover-miner-ui/vite-chat-discover-attempt-actions.ts +++ b/apps/loopover-miner-ui/vite-chat-discover-attempt-actions.ts @@ -3,15 +3,21 @@ import type { Plugin } from "vite"; // Registers discover/attempt chat actions into the shared registry on dev-server start (#6837). Handlers call // the existing miner-ui `requestDiscover` / `requestAttempt` clients — the same POST `/api/discover` and // `/api/attempt` path the routes already serve. No new /api/* route is added here (mirrors -// vite-chat-governor-actions.ts). +// vite-chat-governor-actions.ts). Also registers under `vite preview` (#7228) — the documented +// persistent-service deployment path (systemd/loopover-miner-ui.service.example) only ever invokes +// configurePreviewServer, never configureServer, and registerDiscoverAttemptChatActions is idempotent so +// calling it from either hook is safe. + +function register(): void { + void import("./src/lib/chat-discover-attempt-actions").then((mod) => { + mod.registerDiscoverAttemptChatActions(); + }); +} export function chatDiscoverAttemptActionsPlugin(): Plugin { return { name: "loopover-miner-chat-discover-attempt-actions", - configureServer() { - void import("./src/lib/chat-discover-attempt-actions").then((mod) => { - mod.registerDiscoverAttemptChatActions(); - }); - }, + configureServer: register, + configurePreviewServer: register, }; } diff --git a/apps/loopover-miner-ui/vite-chat-governor-actions.ts b/apps/loopover-miner-ui/vite-chat-governor-actions.ts index 6b2e3cf8ab..b98bf2b292 100644 --- a/apps/loopover-miner-ui/vite-chat-governor-actions.ts +++ b/apps/loopover-miner-ui/vite-chat-governor-actions.ts @@ -2,15 +2,21 @@ import type { Plugin } from "vite"; // Registers governor pause/resume chat actions into the shared registry on dev-server start (#6521). // Handlers call the existing miner-ui `pauseGovernor` / `resumeGovernor` clients — same path as the Ledgers -// buttons. No new /api/governor/* route is added here. +// buttons. No new /api/governor/* route is added here. Also registers under `vite preview` (#7228) — the +// documented persistent-service deployment path (systemd/loopover-miner-ui.service.example) only ever +// invokes configurePreviewServer, never configureServer, and registerGovernorChatActions is idempotent so +// calling it from either hook is safe. + +function register(): void { + void import("./src/lib/chat-governor-actions").then((mod) => { + mod.registerGovernorChatActions(); + }); +} export function chatGovernorActionsPlugin(): Plugin { return { name: "loopover-miner-chat-governor-actions", - configureServer() { - void import("./src/lib/chat-governor-actions").then((mod) => { - mod.registerGovernorChatActions(); - }); - }, + configureServer: register, + configurePreviewServer: register, }; }