import type { ReactNode } from 'react' import type { AppDetailResponse } from '@/models/app' import type { AppSSO } from '@/types/app' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { act, renderHook } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { AppModeEnum } from '@/types/app' import { useMCPServiceCardState } from './use-mcp-service-card' // Mutable mock data for MCP server detail let mockMCPServerDetailData: { id: string status: string server_code: string description: string parameters: Record } | undefined = { id: 'server-123', status: 'active', server_code: 'abc123', description: 'Test server', parameters: {}, } // Mock service hooks vi.mock('@/service/use-tools', () => ({ useUpdateMCPServer: () => ({ mutateAsync: vi.fn().mockResolvedValue({}), }), useRefreshMCPServerCode: () => ({ mutateAsync: vi.fn().mockResolvedValue({}), isPending: false, }), useMCPServerDetail: () => ({ data: mockMCPServerDetailData, }), useInvalidateMCPServerDetail: () => vi.fn(), })) // Mock workflow hook vi.mock('@/service/use-workflow', () => ({ useAppWorkflow: (appId: string) => ({ data: appId ? { graph: { nodes: [ { data: { type: 'start', variables: [{ variable: 'input', label: 'Input' }] } }, ], }, } : undefined, }), })) // Mock app context vi.mock('@/context/app-context', () => ({ useAppContext: () => ({ isCurrentWorkspaceManager: true, isCurrentWorkspaceEditor: true, }), })) // Mock apps service vi.mock('@/service/apps', () => ({ fetchAppDetail: vi.fn().mockResolvedValue({ model_config: { updated_at: '2024-01-01', user_input_form: [], }, }), })) describe('useMCPServiceCardState', () => { const createWrapper = () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }) return ({ children }: { children: ReactNode }) => React.createElement(QueryClientProvider, { client: queryClient }, children) } const createMockAppInfo = (mode: AppModeEnum = AppModeEnum.CHAT): AppDetailResponse & Partial => ({ id: 'app-123', name: 'Test App', mode, api_base_url: 'https://api.example.com/v1', } as AppDetailResponse & Partial) beforeEach(() => { // Reset mock data to default (published server) mockMCPServerDetailData = { id: 'server-123', status: 'active', server_code: 'abc123', description: 'Test server', parameters: {}, } }) describe('Initialization', () => { it('should initialize with correct default values for basic app', () => { const appInfo = createMockAppInfo(AppModeEnum.CHAT) const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(result.current.serverPublished).toBe(true) expect(result.current.serverActivated).toBe(true) expect(result.current.showConfirmDelete).toBe(false) expect(result.current.showMCPServerModal).toBe(false) }) it('should initialize with correct values for workflow app', () => { const appInfo = createMockAppInfo(AppModeEnum.WORKFLOW) const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(result.current.isLoading).toBe(false) }) it('should initialize with correct values for advanced chat app', () => { const appInfo = createMockAppInfo(AppModeEnum.ADVANCED_CHAT) const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(result.current.isLoading).toBe(false) }) }) describe('Server URL Generation', () => { it('should generate correct server URL when published', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(result.current.serverURL).toBe('https://api.example.com/mcp/server/abc123/mcp') }) }) describe('Permission Flags', () => { it('should have isCurrentWorkspaceManager as true', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(result.current.isCurrentWorkspaceManager).toBe(true) }) it('should have toggleDisabled false when editor has permissions', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) // Toggle is not disabled when user has permissions and app is published expect(typeof result.current.toggleDisabled).toBe('boolean') }) it('should have toggleDisabled true when triggerModeDisabled is true', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, true), { wrapper: createWrapper() }, ) expect(result.current.toggleDisabled).toBe(true) }) }) describe('UI State Actions', () => { it('should open confirm delete modal', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(result.current.showConfirmDelete).toBe(false) act(() => { result.current.openConfirmDelete() }) expect(result.current.showConfirmDelete).toBe(true) }) it('should close confirm delete modal', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) act(() => { result.current.openConfirmDelete() }) expect(result.current.showConfirmDelete).toBe(true) act(() => { result.current.closeConfirmDelete() }) expect(result.current.showConfirmDelete).toBe(false) }) it('should open server modal', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(result.current.showMCPServerModal).toBe(false) act(() => { result.current.openServerModal() }) expect(result.current.showMCPServerModal).toBe(true) }) it('should handle server modal hide', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) act(() => { result.current.openServerModal() }) expect(result.current.showMCPServerModal).toBe(true) let hideResult: { shouldDeactivate: boolean } | undefined act(() => { hideResult = result.current.handleServerModalHide(false) }) expect(result.current.showMCPServerModal).toBe(false) expect(hideResult?.shouldDeactivate).toBe(true) }) it('should not deactivate when wasActivated is true', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) let hideResult: { shouldDeactivate: boolean } | undefined act(() => { hideResult = result.current.handleServerModalHide(true) }) expect(hideResult?.shouldDeactivate).toBe(false) }) }) describe('Handler Functions', () => { it('should have handleGenCode function', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(typeof result.current.handleGenCode).toBe('function') }) it('should call handleGenCode and invalidate server detail', async () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) await act(async () => { await result.current.handleGenCode() }) // handleGenCode should complete without error expect(result.current.genLoading).toBe(false) }) it('should have handleStatusChange function', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(typeof result.current.handleStatusChange).toBe('function') }) it('should have invalidateBasicAppConfig function', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(typeof result.current.invalidateBasicAppConfig).toBe('function') }) it('should call invalidateBasicAppConfig', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) // Call the function - should not throw act(() => { result.current.invalidateBasicAppConfig() }) // Function should exist and be callable expect(typeof result.current.invalidateBasicAppConfig).toBe('function') }) }) describe('Status Change', () => { it('should return activated state when status change succeeds', async () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) let statusResult: { activated: boolean } | undefined await act(async () => { statusResult = await result.current.handleStatusChange(true) }) expect(statusResult?.activated).toBe(true) }) it('should return deactivated state when disabling', async () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) let statusResult: { activated: boolean } | undefined await act(async () => { statusResult = await result.current.handleStatusChange(false) }) expect(statusResult?.activated).toBe(false) }) }) describe('Unpublished Server', () => { it('should open modal and return not activated when enabling unpublished server', async () => { // Set mock to return undefined (unpublished server) mockMCPServerDetailData = undefined const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) // Verify server is not published expect(result.current.serverPublished).toBe(false) let statusResult: { activated: boolean } | undefined await act(async () => { statusResult = await result.current.handleStatusChange(true) }) // Should open modal and return not activated expect(result.current.showMCPServerModal).toBe(true) expect(statusResult?.activated).toBe(false) }) }) describe('Loading States', () => { it('should have genLoading state', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(typeof result.current.genLoading).toBe('boolean') }) it('should have isLoading state for basic app', () => { const appInfo = createMockAppInfo(AppModeEnum.CHAT) const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) // Basic app doesn't need workflow, so isLoading should be false expect(result.current.isLoading).toBe(false) }) }) describe('Detail Data', () => { it('should return detail data when available', () => { const appInfo = createMockAppInfo() const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(result.current.detail).toBeDefined() expect(result.current.detail?.id).toBe('server-123') expect(result.current.detail?.status).toBe('active') }) }) describe('Latest Params', () => { it('should return latestParams for workflow app', () => { const appInfo = createMockAppInfo(AppModeEnum.WORKFLOW) const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(Array.isArray(result.current.latestParams)).toBe(true) }) it('should return latestParams for basic app', () => { const appInfo = createMockAppInfo(AppModeEnum.CHAT) const { result } = renderHook( () => useMCPServiceCardState(appInfo, false), { wrapper: createWrapper() }, ) expect(Array.isArray(result.current.latestParams)).toBe(true) }) }) })