diff --git a/frontend/src/api/remoteServers.ts b/frontend/src/api/remoteServers.ts index 4309ed08..02032b5d 100644 --- a/frontend/src/api/remoteServers.ts +++ b/frontend/src/api/remoteServers.ts @@ -38,3 +38,8 @@ export const updateRemoteServer = async (uuid: string, server: Partial => { await client.delete(`/remote-servers/${uuid}`); }; + +export const testRemoteServerConnection = async (uuid: string): Promise<{ address: string }> => { + const { data } = await client.post<{ address: string }>(`/remote-servers/${uuid}/test`); + return data; +}; diff --git a/frontend/src/hooks/__tests__/useImport.test.tsx b/frontend/src/hooks/__tests__/useImport.test.tsx index 50188a47..95aa580c 100644 --- a/frontend/src/hooks/__tests__/useImport.test.tsx +++ b/frontend/src/hooks/__tests__/useImport.test.tsx @@ -3,17 +3,15 @@ import { renderHook, waitFor, act } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import React from 'react' import { useImport } from '../useImport' -import * as api from '../../services/api' +import * as api from '../../api/import' // Mock the API -vi.mock('../../services/api', () => ({ - importAPI: { - status: vi.fn(), - preview: vi.fn(), - upload: vi.fn(), - commit: vi.fn(), - cancel: vi.fn(), - }, +vi.mock('../../api/import', () => ({ + uploadCaddyfile: vi.fn(), + getImportPreview: vi.fn(), + commitImport: vi.fn(), + cancelImport: vi.fn(), + getImportStatus: vi.fn(), })) const createWrapper = () => { @@ -32,7 +30,7 @@ const createWrapper = () => { describe('useImport', () => { beforeEach(() => { vi.clearAllMocks() - vi.mocked(api.importAPI.status).mockResolvedValue({ has_pending: false }) + vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: false }) }) afterEach(() => { @@ -64,9 +62,9 @@ describe('useImport', () => { errors: [], } - vi.mocked(api.importAPI.upload).mockResolvedValue({ session: mockSession }) - vi.mocked(api.importAPI.status).mockResolvedValue({ has_pending: true, session: mockSession }) - vi.mocked(api.importAPI.preview).mockResolvedValue(mockPreview) + vi.mocked(api.uploadCaddyfile).mockResolvedValue({ session: mockSession }) + vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: true, session: mockSession }) + vi.mocked(api.getImportPreview).mockResolvedValue(mockPreview) const { result } = renderHook(() => useImport(), { wrapper: createWrapper() }) @@ -78,13 +76,13 @@ describe('useImport', () => { expect(result.current.session).toEqual(mockSession) }) - expect(api.importAPI.upload).toHaveBeenCalledWith('example.com { reverse_proxy localhost:8080 }', undefined) + expect(api.uploadCaddyfile).toHaveBeenCalledWith('example.com { reverse_proxy localhost:8080 }', undefined) expect(result.current.loading).toBe(false) }) it('handles upload errors', async () => { const mockError = new Error('Upload failed') - vi.mocked(api.importAPI.upload).mockRejectedValue(mockError) + vi.mocked(api.uploadCaddyfile).mockRejectedValue(mockError) const { result } = renderHook(() => useImport(), { wrapper: createWrapper() }) @@ -112,11 +110,11 @@ describe('useImport', () => { updated_at: '2025-01-18T10:00:00Z', } - vi.mocked(api.importAPI.upload).mockResolvedValue({ session: mockSession }) + vi.mocked(api.uploadCaddyfile).mockResolvedValue({ session: mockSession }) // Keep session pending during initial checks so upload retains session state - vi.mocked(api.importAPI.status).mockResolvedValue({ has_pending: true, session: mockSession }) - vi.mocked(api.importAPI.preview).mockResolvedValue({ hosts: [], conflicts: [], errors: [] }) - vi.mocked(api.importAPI.commit).mockResolvedValue({}) + vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: true, session: mockSession }) + vi.mocked(api.getImportPreview).mockResolvedValue({ hosts: [], conflicts: [], errors: [] }) + vi.mocked(api.commitImport).mockResolvedValue({}) const { result } = renderHook(() => useImport(), { wrapper: createWrapper() }) @@ -132,7 +130,7 @@ describe('useImport', () => { await result.current.commit({ 'test.com': 'skip' }) }) - expect(api.importAPI.commit).toHaveBeenCalledWith('session-2', { 'test.com': 'skip' }) + expect(api.commitImport).toHaveBeenCalledWith('session-2', { 'test.com': 'skip' }) await waitFor(() => { expect(result.current.session).toBeNull() @@ -148,10 +146,10 @@ describe('useImport', () => { updated_at: '2025-01-18T10:00:00Z', } - vi.mocked(api.importAPI.upload).mockResolvedValue({ session: mockSession }) - vi.mocked(api.importAPI.status).mockResolvedValue({ has_pending: true, session: mockSession }) - vi.mocked(api.importAPI.preview).mockResolvedValue({ hosts: [], conflicts: [], errors: [] }) - vi.mocked(api.importAPI.cancel).mockResolvedValue(undefined) + vi.mocked(api.uploadCaddyfile).mockResolvedValue({ session: mockSession }) + vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: true, session: mockSession }) + vi.mocked(api.getImportPreview).mockResolvedValue({ hosts: [], conflicts: [], errors: [] }) + vi.mocked(api.cancelImport).mockResolvedValue(undefined) const { result } = renderHook(() => useImport(), { wrapper: createWrapper() }) @@ -167,7 +165,7 @@ describe('useImport', () => { await result.current.cancel() }) - expect(api.importAPI.cancel).toHaveBeenCalledWith('session-3') + expect(api.cancelImport).toHaveBeenCalledWith('session-3') await waitFor(() => { expect(result.current.session).toBeNull() }) @@ -182,12 +180,12 @@ describe('useImport', () => { updated_at: '2025-01-18T10:00:00Z', } - vi.mocked(api.importAPI.upload).mockResolvedValue({ session: mockSession }) - vi.mocked(api.importAPI.status).mockResolvedValue({ has_pending: true, session: mockSession }) - vi.mocked(api.importAPI.preview).mockResolvedValue({ hosts: [], conflicts: [], errors: [] }) + vi.mocked(api.uploadCaddyfile).mockResolvedValue({ session: mockSession }) + vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: true, session: mockSession }) + vi.mocked(api.getImportPreview).mockResolvedValue({ hosts: [], conflicts: [], errors: [] }) const mockError = new Error('Commit failed') - vi.mocked(api.importAPI.commit).mockRejectedValue(mockError) + vi.mocked(api.commitImport).mockRejectedValue(mockError) const { result } = renderHook(() => useImport(), { wrapper: createWrapper() }) diff --git a/frontend/src/hooks/__tests__/useProxyHosts.test.tsx b/frontend/src/hooks/__tests__/useProxyHosts.test.tsx index 2caa0b2a..011c709f 100644 --- a/frontend/src/hooks/__tests__/useProxyHosts.test.tsx +++ b/frontend/src/hooks/__tests__/useProxyHosts.test.tsx @@ -41,7 +41,7 @@ describe('useProxyHosts', () => { { uuid: '2', domain_names: 'app.com', enabled: true, forward_host: 'localhost', forward_port: 3000 }, ] - vi.mocked(api.proxyHostsAPI.list).mockResolvedValue(mockHosts) + vi.mocked(api.getProxyHosts).mockResolvedValue(mockHosts) const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() }) @@ -54,12 +54,12 @@ describe('useProxyHosts', () => { expect(result.current.hosts).toEqual(mockHosts) expect(result.current.error).toBeNull() - expect(api.proxyHostsAPI.list).toHaveBeenCalledOnce() + expect(api.getProxyHosts).toHaveBeenCalledOnce() }) it('handles loading errors', async () => { const mockError = new Error('Failed to fetch') - vi.mocked(api.proxyHostsAPI.list).mockRejectedValue(mockError) + vi.mocked(api.getProxyHosts).mockRejectedValue(mockError) const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() }) @@ -72,11 +72,11 @@ describe('useProxyHosts', () => { }) it('creates a new proxy host', async () => { - vi.mocked(api.proxyHostsAPI.list).mockResolvedValue([]) + vi.mocked(api.getProxyHosts).mockResolvedValue([]) const newHost = { domain_names: 'new.com', forward_host: 'localhost', forward_port: 9000 } const createdHost = { uuid: '3', ...newHost, enabled: true } - vi.mocked(api.proxyHostsAPI.create).mockResolvedValue(createdHost) + vi.mocked(api.createProxyHost).mockResolvedValue(createdHost) const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() }) @@ -88,7 +88,7 @@ describe('useProxyHosts', () => { await result.current.createHost(newHost) }) - expect(api.proxyHostsAPI.create).toHaveBeenCalledWith(newHost) + expect(api.createProxyHost).toHaveBeenCalledWith(newHost) await waitFor(() => { expect(result.current.hosts).toContainEqual(createdHost) }) @@ -96,10 +96,10 @@ describe('useProxyHosts', () => { it('updates an existing proxy host', async () => { const existingHost = { uuid: '1', domain_names: 'test.com', enabled: true, forward_host: 'localhost', forward_port: 8080 } - vi.mocked(api.proxyHostsAPI.list).mockResolvedValue([existingHost]) + vi.mocked(api.getProxyHosts).mockResolvedValue([existingHost]) const updatedHost = { ...existingHost, domain_names: 'updated.com' } - vi.mocked(api.proxyHostsAPI.update).mockResolvedValue(updatedHost) + vi.mocked(api.updateProxyHost).mockResolvedValue(updatedHost) const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() }) @@ -111,7 +111,7 @@ describe('useProxyHosts', () => { await result.current.updateHost('1', { domain_names: 'updated.com' }) }) - expect(api.proxyHostsAPI.update).toHaveBeenCalledWith('1', { domain_names: 'updated.com' }) + expect(api.updateProxyHost).toHaveBeenCalledWith('1', { domain_names: 'updated.com' }) await waitFor(() => { expect(result.current.hosts[0].domain_names).toBe('updated.com') }) @@ -122,8 +122,8 @@ describe('useProxyHosts', () => { { uuid: '1', domain_names: 'test.com', enabled: true, forward_host: 'localhost', forward_port: 8080 }, { uuid: '2', domain_names: 'app.com', enabled: true, forward_host: 'localhost', forward_port: 3000 }, ] - vi.mocked(api.proxyHostsAPI.list).mockResolvedValue(hosts) - vi.mocked(api.proxyHostsAPI.delete).mockResolvedValue(undefined) + vi.mocked(api.getProxyHosts).mockResolvedValue(hosts) + vi.mocked(api.deleteProxyHost).mockResolvedValue(undefined) const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() }) @@ -135,7 +135,7 @@ describe('useProxyHosts', () => { await result.current.deleteHost('1') }) - expect(api.proxyHostsAPI.delete).toHaveBeenCalledWith('1') + expect(api.deleteProxyHost).toHaveBeenCalledWith('1') await waitFor(() => { expect(result.current.hosts).toHaveLength(1) expect(result.current.hosts[0].uuid).toBe('2') @@ -143,9 +143,9 @@ describe('useProxyHosts', () => { }) it('handles create errors', async () => { - vi.mocked(api.proxyHostsAPI.list).mockResolvedValue([]) + vi.mocked(api.getProxyHosts).mockResolvedValue([]) const mockError = new Error('Failed to create') - vi.mocked(api.proxyHostsAPI.create).mockRejectedValue(mockError) + vi.mocked(api.createProxyHost).mockRejectedValue(mockError) const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() }) @@ -158,9 +158,9 @@ describe('useProxyHosts', () => { it('handles update errors', async () => { const host = { uuid: '1', domain_names: 'test.com', enabled: true, forward_host: 'localhost', forward_port: 8080 } - vi.mocked(api.proxyHostsAPI.list).mockResolvedValue([host]) + vi.mocked(api.getProxyHosts).mockResolvedValue([host]) const mockError = new Error('Failed to update') - vi.mocked(api.proxyHostsAPI.update).mockRejectedValue(mockError) + vi.mocked(api.updateProxyHost).mockRejectedValue(mockError) const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() }) @@ -173,9 +173,9 @@ describe('useProxyHosts', () => { it('handles delete errors', async () => { const host = { uuid: '1', domain_names: 'test.com', enabled: true, forward_host: 'localhost', forward_port: 8080 } - vi.mocked(api.proxyHostsAPI.list).mockResolvedValue([host]) + vi.mocked(api.getProxyHosts).mockResolvedValue([host]) const mockError = new Error('Failed to delete') - vi.mocked(api.proxyHostsAPI.delete).mockRejectedValue(mockError) + vi.mocked(api.deleteProxyHost).mockRejectedValue(mockError) const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() }) diff --git a/frontend/src/hooks/__tests__/useRemoteServers.test.tsx b/frontend/src/hooks/__tests__/useRemoteServers.test.tsx index 1658f575..1b51a96e 100644 --- a/frontend/src/hooks/__tests__/useRemoteServers.test.tsx +++ b/frontend/src/hooks/__tests__/useRemoteServers.test.tsx @@ -3,18 +3,15 @@ import { renderHook, waitFor, act } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import React from 'react' import { useRemoteServers } from '../useRemoteServers' -import * as api from '../../services/api' +import * as api from '../../api/remoteServers' // Mock the API -vi.mock('../../services/api', () => ({ - remoteServersAPI: { - list: vi.fn(), - get: vi.fn(), - create: vi.fn(), - update: vi.fn(), - delete: vi.fn(), - test: vi.fn(), - }, +vi.mock('../../api/remoteServers', () => ({ + getRemoteServers: vi.fn(), + createRemoteServer: vi.fn(), + updateRemoteServer: vi.fn(), + deleteRemoteServer: vi.fn(), + testRemoteServerConnection: vi.fn(), })) const createWrapper = () => { @@ -45,7 +42,7 @@ describe('useRemoteServers', () => { { uuid: '2', name: 'Server 2', host: '192.168.1.100', port: 3000, enabled: false }, ] - vi.mocked(api.remoteServersAPI.list).mockResolvedValue(mockServers) + vi.mocked(api.getRemoteServers).mockResolvedValue(mockServers) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) @@ -58,7 +55,7 @@ describe('useRemoteServers', () => { expect(result.current.servers).toEqual(mockServers) expect(result.current.error).toBeNull() - expect(api.remoteServersAPI.list).toHaveBeenCalledOnce() + expect(api.getRemoteServers).toHaveBeenCalledOnce() }) it('filters enabled servers', async () => { @@ -68,7 +65,7 @@ describe('useRemoteServers', () => { { uuid: '3', name: 'Server 3', host: '10.0.0.1', port: 9000, enabled: true }, ] - vi.mocked(api.remoteServersAPI.list).mockResolvedValue(mockServers) + vi.mocked(api.getRemoteServers).mockResolvedValue(mockServers) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) @@ -85,7 +82,7 @@ describe('useRemoteServers', () => { it('handles loading errors', async () => { const mockError = new Error('Network error') - vi.mocked(api.remoteServersAPI.list).mockRejectedValue(mockError) + vi.mocked(api.getRemoteServers).mockRejectedValue(mockError) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) @@ -99,11 +96,11 @@ describe('useRemoteServers', () => { }) it('creates a new remote server', async () => { - vi.mocked(api.remoteServersAPI.list).mockResolvedValue([]) + vi.mocked(api.getRemoteServers).mockResolvedValue([]) const newServer = { name: 'New Server', host: 'new.local', port: 5000, enabled: true, provider: 'generic' } const createdServer = { uuid: '4', ...newServer } - vi.mocked(api.remoteServersAPI.create).mockResolvedValue(createdServer) + vi.mocked(api.createRemoteServer).mockResolvedValue(createdServer) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) @@ -115,7 +112,7 @@ describe('useRemoteServers', () => { await result.current.createServer(newServer) }) - expect(api.remoteServersAPI.create).toHaveBeenCalledWith(newServer) + expect(api.createRemoteServer).toHaveBeenCalledWith(newServer) await waitFor(() => { expect(result.current.servers).toContainEqual(createdServer) }) @@ -123,10 +120,10 @@ describe('useRemoteServers', () => { it('updates an existing remote server', async () => { const existingServer = { uuid: '1', name: 'Server 1', host: 'localhost', port: 8080, enabled: true } - vi.mocked(api.remoteServersAPI.list).mockResolvedValue([existingServer]) + vi.mocked(api.getRemoteServers).mockResolvedValue([existingServer]) const updatedServer = { ...existingServer, name: 'Updated Server' } - vi.mocked(api.remoteServersAPI.update).mockResolvedValue(updatedServer) + vi.mocked(api.updateRemoteServer).mockResolvedValue(updatedServer) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) @@ -138,7 +135,7 @@ describe('useRemoteServers', () => { await result.current.updateServer('1', { name: 'Updated Server' }) }) - expect(api.remoteServersAPI.update).toHaveBeenCalledWith('1', { name: 'Updated Server' }) + expect(api.updateRemoteServer).toHaveBeenCalledWith('1', { name: 'Updated Server' }) await waitFor(() => { expect(result.current.servers[0].name).toBe('Updated Server') }) @@ -149,8 +146,8 @@ describe('useRemoteServers', () => { { uuid: '1', name: 'Server 1', host: 'localhost', port: 8080, enabled: true }, { uuid: '2', name: 'Server 2', host: '192.168.1.100', port: 3000, enabled: false }, ] - vi.mocked(api.remoteServersAPI.list).mockResolvedValue(servers) - vi.mocked(api.remoteServersAPI.delete).mockResolvedValue(undefined) + vi.mocked(api.getRemoteServers).mockResolvedValue(servers) + vi.mocked(api.deleteRemoteServer).mockResolvedValue(undefined) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) @@ -162,7 +159,7 @@ describe('useRemoteServers', () => { await result.current.deleteServer('1') }) - expect(api.remoteServersAPI.delete).toHaveBeenCalledWith('1') + expect(api.deleteRemoteServer).toHaveBeenCalledWith('1') await waitFor(() => { expect(result.current.servers).toHaveLength(1) expect(result.current.servers[0].uuid).toBe('2') @@ -170,9 +167,9 @@ describe('useRemoteServers', () => { }) it('tests server connection', async () => { - vi.mocked(api.remoteServersAPI.list).mockResolvedValue([]) + vi.mocked(api.getRemoteServers).mockResolvedValue([]) const testResult = { reachable: true, address: 'localhost:8080' } - vi.mocked(api.remoteServersAPI.test).mockResolvedValue(testResult) + vi.mocked(api.testRemoteServerConnection).mockResolvedValue(testResult) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) @@ -182,14 +179,14 @@ describe('useRemoteServers', () => { const response = await result.current.testConnection('1') - expect(api.remoteServersAPI.test).toHaveBeenCalledWith('1') + expect(api.testRemoteServerConnection).toHaveBeenCalledWith('1') expect(response).toEqual(testResult) }) it('handles create errors', async () => { - vi.mocked(api.remoteServersAPI.list).mockResolvedValue([]) + vi.mocked(api.getRemoteServers).mockResolvedValue([]) const mockError = new Error('Failed to create') - vi.mocked(api.remoteServersAPI.create).mockRejectedValue(mockError) + vi.mocked(api.createRemoteServer).mockRejectedValue(mockError) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) @@ -202,9 +199,9 @@ describe('useRemoteServers', () => { it('handles update errors', async () => { const server = { uuid: '1', name: 'Server 1', host: 'localhost', port: 8080, enabled: true } - vi.mocked(api.remoteServersAPI.list).mockResolvedValue([server]) + vi.mocked(api.getRemoteServers).mockResolvedValue([server]) const mockError = new Error('Failed to update') - vi.mocked(api.remoteServersAPI.update).mockRejectedValue(mockError) + vi.mocked(api.updateRemoteServer).mockRejectedValue(mockError) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) @@ -217,9 +214,9 @@ describe('useRemoteServers', () => { it('handles delete errors', async () => { const server = { uuid: '1', name: 'Server 1', host: 'localhost', port: 8080, enabled: true } - vi.mocked(api.remoteServersAPI.list).mockResolvedValue([server]) + vi.mocked(api.getRemoteServers).mockResolvedValue([server]) const mockError = new Error('Failed to delete') - vi.mocked(api.remoteServersAPI.delete).mockRejectedValue(mockError) + vi.mocked(api.deleteRemoteServer).mockRejectedValue(mockError) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) @@ -231,9 +228,9 @@ describe('useRemoteServers', () => { }) it('handles connection test errors', async () => { - vi.mocked(api.remoteServersAPI.list).mockResolvedValue([]) + vi.mocked(api.getRemoteServers).mockResolvedValue([]) const mockError = new Error('Connection failed') - vi.mocked(api.remoteServersAPI.test).mockRejectedValue(mockError) + vi.mocked(api.testRemoteServerConnection).mockRejectedValue(mockError) const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() }) diff --git a/frontend/src/hooks/useRemoteServers.ts b/frontend/src/hooks/useRemoteServers.ts index 03bd2c3c..279b1544 100644 --- a/frontend/src/hooks/useRemoteServers.ts +++ b/frontend/src/hooks/useRemoteServers.ts @@ -4,6 +4,7 @@ import { createRemoteServer, updateRemoteServer, deleteRemoteServer, + testRemoteServerConnection, RemoteServer } from '../api/remoteServers'; @@ -39,6 +40,10 @@ export function useRemoteServers(enabledOnly = false) { }, }); + const testConnectionMutation = useMutation({ + mutationFn: testRemoteServerConnection, + }); + return { servers: query.data || [], loading: query.isLoading, @@ -46,9 +51,11 @@ export function useRemoteServers(enabledOnly = false) { createServer: createMutation.mutateAsync, updateServer: (uuid: string, data: Partial) => updateMutation.mutateAsync({ uuid, data }), deleteServer: deleteMutation.mutateAsync, + testConnection: testConnectionMutation.mutateAsync, isCreating: createMutation.isPending, isUpdating: updateMutation.isPending, isDeleting: deleteMutation.isPending, + isTestingConnection: testConnectionMutation.isPending, }; }