From 483256ad92cb70f3f184d1f6628e9fbeaabdf82a Mon Sep 17 00:00:00 2001 From: SaladDay Date: Thu, 30 Jul 2026 14:59:48 +0000 Subject: [PATCH] refactor: remove redundant proxy query paths --- src/App.tsx | 8 +- src/components/proxy/ProxyPanel.tsx | 5 +- src/hooks/useProxyStatus.ts | 107 +++----------- src/lib/api/proxy.ts | 36 +---- src/lib/query/failover.ts | 5 +- src/lib/query/mutations.ts | 3 +- src/lib/query/proxy.ts | 170 +++++------------------ tests/components/SettingsDialog.test.tsx | 11 +- tests/hooks/useProxyStatus.test.tsx | 17 ++- 9 files changed, 89 insertions(+), 273 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 227deae61..e794ab131 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,7 +29,7 @@ import { import { getCurrentWindow } from "@tauri-apps/api/window"; import type { Provider, VisibleApps } from "@/types"; import type { EnvConflict } from "@/types/env"; -import { useProvidersQuery, useSettingsQuery } from "@/lib/query"; +import { proxyKeys, useProvidersQuery, useSettingsQuery } from "@/lib/query"; import { providersApi, settingsApi, @@ -389,8 +389,10 @@ function App() { await queryClient.invalidateQueries({ queryKey: ["profiles"] }); await queryClient.invalidateQueries({ queryKey: ["mcp", "all"] }); await queryClient.invalidateQueries({ queryKey: ["skills"] }); - await queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] }); - await queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); + await queryClient.invalidateQueries({ + queryKey: proxyKeys.takeoverStatus, + }); + await queryClient.invalidateQueries({ queryKey: proxyKeys.status }); await queryClient.invalidateQueries({ queryKey: ["providers", "claude-desktop"], }); diff --git a/src/components/proxy/ProxyPanel.tsx b/src/components/proxy/ProxyPanel.tsx index a7b6fbda5..458fb92ff 100644 --- a/src/components/proxy/ProxyPanel.tsx +++ b/src/components/proxy/ProxyPanel.tsx @@ -15,12 +15,12 @@ import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { ToggleRow } from "@/components/ui/toggle-row"; -import { useProxyStatus } from "@/hooks/useProxyStatus"; import { toast } from "sonner"; import { useFailoverQueue } from "@/lib/query/failover"; import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge"; import { useProviderHealth } from "@/lib/query/failover"; import { + useProxyStatusQuery, useProxyTakeoverStatus, useSetProxyTakeoverForApp, useGlobalProxyConfig, @@ -45,7 +45,8 @@ export function ProxyPanel({ isProxyPending, }: ProxyPanelProps) { const { t } = useTranslation(); - const { status, isRunning } = useProxyStatus(); + const { data: status } = useProxyStatusQuery(); + const isRunning = status?.running ?? false; // 获取应用接管状态 const { data: takeoverStatus } = useProxyTakeoverStatus(); diff --git a/src/hooks/useProxyStatus.ts b/src/hooks/useProxyStatus.ts index c7995e154..166e6dac0 100644 --- a/src/hooks/useProxyStatus.ts +++ b/src/hooks/useProxyStatus.ts @@ -2,15 +2,15 @@ * 代理服务状态管理 Hook */ -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { invoke } from "@tauri-apps/api/core"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; -import type { - ProxyStatus, - ProxyServerInfo, - ProxyTakeoverStatus, -} from "@/types/proxy"; +import { proxyApi } from "@/lib/api/proxy"; +import { + proxyKeys, + useProxyStatusQuery, + useProxyTakeoverStatus, +} from "@/lib/query/proxy"; import { extractErrorMessage } from "@/utils/errorUtils"; /** @@ -21,25 +21,14 @@ export function useProxyStatus() { const { t } = useTranslation(); // 查询状态(自动轮询) - const { data: status, isLoading } = useQuery({ - queryKey: ["proxyStatus"], - queryFn: () => invoke("get_proxy_status"), - // 仅在服务运行时轮询 - refetchInterval: (query) => (query.state.data?.running ? 2000 : false), - // 保持之前的数据,避免闪烁 - placeholderData: (previousData) => previousData, - }); + const { data: status } = useProxyStatusQuery(); // 查询各应用接管状态 - const { data: takeoverStatus } = useQuery({ - queryKey: ["proxyTakeoverStatus"], - queryFn: () => invoke("get_proxy_takeover_status"), - placeholderData: (previousData) => previousData, - }); + const { data: takeoverStatus } = useProxyTakeoverStatus(false); // 启动服务器(总开关:仅启动服务,不接管) const startProxyServerMutation = useMutation({ - mutationFn: () => invoke("start_proxy_server"), + mutationFn: () => proxyApi.startProxyServer(), onSuccess: (info) => { toast.success( t("proxy.server.started", { @@ -49,7 +38,7 @@ export function useProxyStatus() { }), { closeButton: true }, ); - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); + queryClient.invalidateQueries({ queryKey: proxyKeys.status }); }, onError: (error: Error) => { const detail = @@ -66,7 +55,7 @@ export function useProxyStatus() { // 停止服务器(仅停止服务,不改写/恢复其它应用接管状态) const stopProxyServerMutation = useMutation({ - mutationFn: () => invoke("stop_proxy_server"), + mutationFn: () => proxyApi.stopProxyServer(), onSuccess: () => { toast.success( t("proxy.server.stopped", { @@ -74,7 +63,7 @@ export function useProxyStatus() { }), { closeButton: true }, ); - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); + queryClient.invalidateQueries({ queryKey: proxyKeys.status }); }, onError: (error: Error) => { const detail = @@ -91,7 +80,7 @@ export function useProxyStatus() { // 停止服务器(总开关关闭:强制恢复所有已接管的 Live 配置) const stopWithRestoreMutation = useMutation({ - mutationFn: () => invoke("stop_proxy_with_restore"), + mutationFn: () => proxyApi.stopProxyWithRestore(), onSuccess: () => { toast.success( t("proxy.stoppedWithRestore", { @@ -99,8 +88,8 @@ export function useProxyStatus() { }), { closeButton: true }, ); - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); - queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] }); + queryClient.invalidateQueries({ queryKey: proxyKeys.status }); + queryClient.invalidateQueries({ queryKey: proxyKeys.takeoverStatus }); // 彻底删除所有供应商健康状态缓存(后端已清空数据库记录) queryClient.removeQueries({ queryKey: ["providerHealth"] }); // 彻底删除所有熔断器统计缓存(代理停止后熔断器状态已重置) @@ -123,7 +112,7 @@ export function useProxyStatus() { // 按应用开启/关闭接管 const setTakeoverForAppMutation = useMutation({ mutationFn: ({ appType, enabled }: { appType: string; enabled: boolean }) => - invoke("set_proxy_takeover_for_app", { appType, enabled }), + proxyApi.setProxyTakeoverForApp(appType, enabled), onSuccess: (_data, variables) => { const appLabel = variables.appType === "claude" @@ -149,8 +138,8 @@ export function useProxyStatus() { { closeButton: true }, ); - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); - queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] }); + queryClient.invalidateQueries({ queryKey: proxyKeys.status }); + queryClient.invalidateQueries({ queryKey: proxyKeys.takeoverStatus }); }, onError: (error: Error) => { const detail = @@ -165,60 +154,10 @@ export function useProxyStatus() { }, }); - // 代理模式切换供应商(热切换) - const switchProxyProviderMutation = useMutation({ - mutationFn: ({ - appType, - providerId, - }: { - appType: string; - providerId: string; - }) => invoke("switch_proxy_provider", { appType, providerId }), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); - }, - onError: (error: Error) => { - const detail = - extractErrorMessage(error) || - t("common.unknown", { defaultValue: "未知错误" }); - toast.error( - t("proxy.switchFailed", { - error: detail, - defaultValue: `切换失败: ${detail}`, - }), - ); - }, - }); - - // 检查是否运行中 - const checkRunning = async () => { - try { - return await invoke("is_proxy_running"); - } catch { - return false; - } - }; - - // 检查接管状态 - const checkTakeoverActive = async () => { - try { - return await invoke("is_live_takeover_active"); - } catch { - return false; - } - }; - return { status, - isLoading, isRunning: status?.running || false, takeoverStatus, - isTakeoverActive: - takeoverStatus?.claude || - takeoverStatus?.codex || - takeoverStatus?.gemini || - takeoverStatus?.grokbuild || - false, // 启动/停止(总开关) startProxyServer: startProxyServerMutation.mutateAsync, @@ -228,17 +167,9 @@ export function useProxyStatus() { // 按应用接管开关 setTakeoverForApp: setTakeoverForAppMutation.mutateAsync, - // 代理模式下切换供应商 - switchProxyProvider: switchProxyProviderMutation.mutateAsync, - - // 状态检查 - checkRunning, - checkTakeoverActive, - // 加载状态 isStarting: startProxyServerMutation.isPending, isStoppingServer: stopProxyServerMutation.isPending, - isStopping: stopWithRestoreMutation.isPending, isPending: startProxyServerMutation.isPending || stopProxyServerMutation.isPending || diff --git a/src/lib/api/proxy.ts b/src/lib/api/proxy.ts index 83f203134..cc76ee4fd 100644 --- a/src/lib/api/proxy.ts +++ b/src/lib/api/proxy.ts @@ -1,6 +1,5 @@ import { invoke } from "@tauri-apps/api/core"; import type { - ProxyConfig, ProxyStatus, ProxyServerInfo, ProxyTakeoverStatus, @@ -16,6 +15,11 @@ export const proxyApi = { return invoke("start_proxy_server"); }, + // 停止代理服务器(不恢复已接管配置) + async stopProxyServer(): Promise { + return invoke("stop_proxy_server"); + }, + // 停止代理服务器并恢复配置 async stopProxyWithRestore(): Promise { return invoke("stop_proxy_with_restore"); @@ -26,24 +30,6 @@ export const proxyApi = { return invoke("get_proxy_status"); }, - // 检查代理服务器是否正在运行 - async isProxyRunning(): Promise { - return invoke("is_proxy_running"); - }, - - // 检查是否处于接管模式 - async isLiveTakeoverActive(): Promise { - return invoke("is_live_takeover_active"); - }, - - // 代理模式下切换供应商 - async switchProxyProvider( - appType: string, - providerId: string, - ): Promise { - return invoke("switch_proxy_provider", { appType, providerId }); - }, - // ========== 接管状态 API ========== // 获取各应用接管状态 @@ -59,18 +45,6 @@ export const proxyApi = { return invoke("set_proxy_takeover_for_app", { appType, enabled }); }, - // ========== Legacy 代理配置 API (兼容) ========== - - // 获取代理配置(旧版 v2 兼容接口) - async getProxyConfig(): Promise { - return invoke("get_proxy_config"); - }, - - // 更新代理配置(旧版 v2 兼容接口) - async updateProxyConfig(config: ProxyConfig): Promise { - return invoke("update_proxy_config", { config }); - }, - // ========== v3+ 全局/应用级配置 API ========== // 获取全局代理配置 diff --git a/src/lib/query/failover.ts b/src/lib/query/failover.ts index d9ba9b9b9..b8b50d469 100644 --- a/src/lib/query/failover.ts +++ b/src/lib/query/failover.ts @@ -3,6 +3,7 @@ import { failoverApi } from "@/lib/api/failover"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import { extractErrorMessage } from "@/utils/errorUtils"; +import { proxyKeys } from "@/lib/query/proxy"; // ========== 熔断器 Hooks ========== @@ -47,7 +48,7 @@ export function useResetCircuitBreaker() { }); // 刷新代理状态(更新 active_targets) queryClient.invalidateQueries({ - queryKey: ["proxyStatus"], + queryKey: proxyKeys.status, }); }, }); @@ -283,7 +284,7 @@ export function useSetAutoFailoverEnabled() { queryKey: ["providers", variables.appType], }); queryClient.invalidateQueries({ - queryKey: ["proxyStatus"], + queryKey: proxyKeys.status, }); }, }); diff --git a/src/lib/query/mutations.ts b/src/lib/query/mutations.ts index 29a08b7a1..8d6e8bd0f 100644 --- a/src/lib/query/mutations.ts +++ b/src/lib/query/mutations.ts @@ -9,6 +9,7 @@ import { extractErrorMessage } from "@/utils/errorUtils"; import { generateUUID } from "@/utils/uuid"; import { openclawKeys } from "@/hooks/useOpenClaw"; import { invalidateHermesProviderCaches } from "@/hooks/useHermes"; +import { proxyKeys } from "@/lib/query/proxy"; import { usageKeys } from "@/lib/query/usage"; import { CODEX_OFFICIAL_PROVIDER_ID, @@ -286,7 +287,7 @@ export const useSwitchProviderMutation = (appId: AppId) => { onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["providers", appId] }); if (appId === "claude-desktop") { - await queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); + await queryClient.invalidateQueries({ queryKey: proxyKeys.status }); await queryClient.invalidateQueries({ queryKey: ["claudeDesktopStatus"], }); diff --git a/src/lib/query/proxy.ts b/src/lib/query/proxy.ts index 451d39769..696451110 100644 --- a/src/lib/query/proxy.ts +++ b/src/lib/query/proxy.ts @@ -2,90 +2,54 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { proxyApi } from "@/lib/api/proxy"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; -import type { GlobalProxyConfig, AppProxyConfig } from "@/types/proxy"; +import type { + GlobalProxyConfig, + AppProxyConfig, + ProxyTakeoverStatus, +} from "@/types/proxy"; + +export const proxyKeys = { + status: ["proxyStatus"] as const, + takeoverStatus: ["proxyTakeoverStatus"] as const, + globalConfig: ["globalProxyConfig"] as const, + appConfig: (appType: string) => ["appProxyConfig", appType] as const, +}; // ========== 代理服务器状态 Hooks ========== /** * 获取代理服务器状态 */ -export function useProxyStatus() { +export function useProxyStatusQuery() { return useQuery({ - queryKey: ["proxyStatus"], + queryKey: proxyKeys.status, queryFn: () => proxyApi.getProxyStatus(), - refetchInterval: 5000, // 每 5 秒刷新一次 - }); -} - -/** - * 检查代理服务器是否运行 - */ -export function useIsProxyRunning() { - return useQuery({ - queryKey: ["proxyRunning"], - queryFn: () => proxyApi.isProxyRunning(), - refetchInterval: 2000, - }); -} - -/** - * 检查是否处于接管模式 - */ -export function useIsLiveTakeoverActive() { - return useQuery({ - queryKey: ["liveTakeoverActive"], - queryFn: () => proxyApi.isLiveTakeoverActive(), - refetchInterval: 2000, + // 仅在服务运行时轮询 + refetchInterval: (query) => (query.state.data?.running ? 2000 : false), + // 保持之前的数据,避免闪烁 + placeholderData: (previousData) => previousData, }); } /** * 获取各应用接管状态 */ -export function useProxyTakeoverStatus() { +export function useProxyTakeoverStatus(poll = true) { return useQuery({ - queryKey: ["proxyTakeoverStatus"], + queryKey: proxyKeys.takeoverStatus, queryFn: () => proxyApi.getProxyTakeoverStatus(), - refetchInterval: 2000, + refetchInterval: poll ? 2000 : false, + ...(poll + ? {} + : { + placeholderData: (previousData: ProxyTakeoverStatus | undefined) => + previousData, + }), }); } // ========== 代理服务器控制 Hooks ========== -/** - * 启动代理服务器 - */ -export function useStartProxyServer() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: () => proxyApi.startProxyServer(), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); - queryClient.invalidateQueries({ queryKey: ["proxyRunning"] }); - queryClient.invalidateQueries({ queryKey: ["liveTakeoverActive"] }); - queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] }); - }, - }); -} - -/** - * 停止代理服务器 - */ -export function useStopProxyServer() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: () => proxyApi.stopProxyWithRestore(), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); - queryClient.invalidateQueries({ queryKey: ["proxyRunning"] }); - queryClient.invalidateQueries({ queryKey: ["liveTakeoverActive"] }); - queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] }); - }, - }); -} - /** * 设置应用接管状态 */ @@ -96,75 +60,11 @@ export function useSetProxyTakeoverForApp() { mutationFn: ({ appType, enabled }: { appType: string; enabled: boolean }) => proxyApi.setProxyTakeoverForApp(appType, enabled), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] }); - queryClient.invalidateQueries({ queryKey: ["liveTakeoverActive"] }); + queryClient.invalidateQueries({ queryKey: proxyKeys.takeoverStatus }); }, }); } -/** - * 代理模式下切换供应商 - */ -export function useSwitchProxyProvider() { - const queryClient = useQueryClient(); - const { t } = useTranslation(); - - return useMutation({ - mutationFn: ({ - appType, - providerId, - }: { - appType: string; - providerId: string; - }) => proxyApi.switchProxyProvider(appType, providerId), - onSuccess: (_, variables) => { - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); - queryClient.invalidateQueries({ - queryKey: ["providers", variables.appType], - }); - }, - onError: (error: Error) => { - toast.error(t("proxy.switchFailed", { error: error.message })); - }, - }); -} - -// ========== Legacy 代理配置 Hooks (兼容) ========== - -/** - * 获取代理配置(旧版) - */ -export function useProxyConfig() { - const queryClient = useQueryClient(); - const { t } = useTranslation(); - - const { data: config, isLoading } = useQuery({ - queryKey: ["proxyConfig"], - queryFn: () => proxyApi.getProxyConfig(), - }); - - const updateMutation = useMutation({ - mutationFn: proxyApi.updateProxyConfig, - onSuccess: () => { - toast.success(t("proxy.settings.toast.saved"), { closeButton: true }); - queryClient.invalidateQueries({ queryKey: ["proxyConfig"] }); - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); - }, - onError: (error: Error) => { - toast.error( - t("proxy.settings.toast.saveFailed", { error: error.message }), - ); - }, - }); - - return { - config, - isLoading, - updateConfig: updateMutation.mutateAsync, - isUpdating: updateMutation.isPending, - }; -} - // ========== v3+ 全局/应用级配置 Hooks ========== /** @@ -172,7 +72,7 @@ export function useProxyConfig() { */ export function useGlobalProxyConfig() { return useQuery({ - queryKey: ["globalProxyConfig"], + queryKey: proxyKeys.globalConfig, queryFn: () => proxyApi.getGlobalProxyConfig(), }); } @@ -189,9 +89,8 @@ export function useUpdateGlobalProxyConfig() { proxyApi.updateGlobalProxyConfig(config), onSuccess: () => { toast.success(t("proxy.settings.toast.saved"), { closeButton: true }); - queryClient.invalidateQueries({ queryKey: ["globalProxyConfig"] }); - queryClient.invalidateQueries({ queryKey: ["proxyConfig"] }); - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); + queryClient.invalidateQueries({ queryKey: proxyKeys.globalConfig }); + queryClient.invalidateQueries({ queryKey: proxyKeys.status }); }, onError: (error: Error) => { toast.error( @@ -206,7 +105,7 @@ export function useUpdateGlobalProxyConfig() { */ export function useAppProxyConfig(appType: string) { return useQuery({ - queryKey: ["appProxyConfig", appType], + queryKey: proxyKeys.appConfig(appType), queryFn: () => proxyApi.getProxyConfigForApp(appType), enabled: !!appType, }); @@ -225,14 +124,13 @@ export function useUpdateAppProxyConfig() { onSuccess: (_, variables) => { toast.success(t("proxy.settings.toast.saved"), { closeButton: true }); queryClient.invalidateQueries({ - queryKey: ["appProxyConfig", variables.appType], + queryKey: proxyKeys.appConfig(variables.appType), }); queryClient.invalidateQueries({ queryKey: ["autoFailoverEnabled", variables.appType], }); - queryClient.invalidateQueries({ queryKey: ["proxyConfig"] }); queryClient.invalidateQueries({ queryKey: ["circuitBreakerConfig"] }); - queryClient.invalidateQueries({ queryKey: ["proxyStatus"] }); + queryClient.invalidateQueries({ queryKey: proxyKeys.status }); }, onError: (error: Error) => { toast.error( diff --git a/tests/components/SettingsDialog.test.tsx b/tests/components/SettingsDialog.test.tsx index f8fcdc631..e5ca56dcd 100644 --- a/tests/components/SettingsDialog.test.tsx +++ b/tests/components/SettingsDialog.test.tsx @@ -22,17 +22,10 @@ vi.mock("react-i18next", () => ({ vi.mock("@/hooks/useProxyStatus", () => ({ useProxyStatus: () => ({ - status: null, - isLoading: false, isRunning: false, - isTakeoverActive: false, - startWithTakeover: vi.fn(), + takeoverStatus: null, + startProxyServer: vi.fn(), stopWithRestore: vi.fn(), - switchProxyProvider: vi.fn(), - checkRunning: vi.fn(), - checkTakeoverActive: vi.fn(), - isStarting: false, - isStopping: false, isPending: false, }), })); diff --git a/tests/hooks/useProxyStatus.test.tsx b/tests/hooks/useProxyStatus.test.tsx index fe3575585..675f5fb46 100644 --- a/tests/hooks/useProxyStatus.test.tsx +++ b/tests/hooks/useProxyStatus.test.tsx @@ -3,6 +3,7 @@ import { renderHook, act, waitFor } from "@testing-library/react"; import { QueryClientProvider } from "@tanstack/react-query"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { useProxyStatus } from "@/hooks/useProxyStatus"; +import { proxyKeys } from "@/lib/query/proxy"; import { createTestQueryClient } from "../utils/testQueryClient"; const toastSuccessMock = vi.fn(); @@ -99,12 +100,19 @@ describe("useProxyStatus", () => { }); }); + it("keeps the established proxy query key shapes", () => { + expect(proxyKeys.status).toEqual(["proxyStatus"]); + expect(proxyKeys.takeoverStatus).toEqual(["proxyTakeoverStatus"]); + expect(proxyKeys.globalConfig).toEqual(["globalProxyConfig"]); + expect(proxyKeys.appConfig("claude")).toEqual(["appProxyConfig", "claude"]); + }); + it("shows interpolated address and port after proxy server starts", async () => { const { wrapper } = createWrapper(); const { result } = renderHook(() => useProxyStatus(), { wrapper }); await waitFor(() => { - expect(result.current.isLoading).toBe(false); + expect(result.current.status).toBeDefined(); }); await act(async () => { @@ -115,5 +123,12 @@ describe("useProxyStatus", () => { "代理服务已启动 - 127.0.0.1:15721", { closeButton: true }, ); + + await act(async () => { + await result.current.stopProxyServer(); + }); + + expect(invokeMock).toHaveBeenCalledWith("start_proxy_server"); + expect(invokeMock).toHaveBeenCalledWith("stop_proxy_server"); }); });