diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 805d9c39..88d59993 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,7 @@ jobs: uses: actions/setup-go@v5 with: go-version: '1.22' + cache-dependency-path: backend/go.sum - name: Run Go tests working-directory: backend run: | diff --git a/Dockerfile b/Dockerfile index a29b35d1..28192106 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,10 @@ WORKDIR /app/frontend # Copy frontend package files COPY frontend/package*.json ./ + +# Set environment to bypass native binary requirement for cross-arch builds +ENV npm_config_rollup_skip_nodejs_native=1 + RUN npm ci # Copy frontend source and build diff --git a/frontend/src/hooks/__tests__/useImport.test.ts b/frontend/src/hooks/__tests__/useImport.test.ts index a81cbb3c..6abbc526 100644 --- a/frontend/src/hooks/__tests__/useImport.test.ts +++ b/frontend/src/hooks/__tests__/useImport.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import { renderHook, waitFor } from '@testing-library/react' +import { renderHook, waitFor, act } from '@testing-library/react' import { useImport } from '../useImport' import * as api from '../../services/api' @@ -55,7 +55,9 @@ describe('useImport', () => { const { result } = renderHook(() => useImport()) - await result.current.upload('example.com { reverse_proxy localhost:8080 }') + await act(async () => { + await result.current.upload('example.com { reverse_proxy localhost:8080 }') + }) await waitFor(() => { expect(result.current.session).toEqual(mockSession) @@ -71,9 +73,19 @@ describe('useImport', () => { const { result } = renderHook(() => useImport()) - await expect(result.current.upload('invalid')).rejects.toThrow('Upload failed') + let threw = false + await act(async () => { + try { + await result.current.upload('invalid') + } catch { + threw = true + } + }) + expect(threw).toBe(true) - expect(result.current.error).toBe('Upload failed') + await waitFor(() => { + expect(result.current.error).toBe('Upload failed') + }) }) it('commits import with resolutions', async () => { @@ -86,21 +98,24 @@ describe('useImport', () => { } vi.mocked(api.importAPI.upload).mockResolvedValue({ session: mockSession }) - vi.mocked(api.importAPI.status) - .mockResolvedValueOnce({ has_pending: true, session: mockSession }) - .mockResolvedValueOnce({ has_pending: false }) + // 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({}) const { result } = renderHook(() => useImport()) - await result.current.upload('test') + await act(async () => { + await result.current.upload('test') + }) await waitFor(() => { expect(result.current.session).toEqual(mockSession) }) - await result.current.commit({ 'test.com': 'skip' }) + await act(async () => { + await result.current.commit({ 'test.com': 'skip' }) + }) expect(api.importAPI.commit).toHaveBeenCalledWith('session-2', { 'test.com': 'skip' }) @@ -125,16 +140,22 @@ describe('useImport', () => { const { result } = renderHook(() => useImport()) - await result.current.upload('test') + await act(async () => { + await result.current.upload('test') + }) await waitFor(() => { expect(result.current.session).toEqual(mockSession) }) - await result.current.cancel() + await act(async () => { + await result.current.cancel() + }) expect(api.importAPI.cancel).toHaveBeenCalledWith('session-3') - expect(result.current.session).toBeNull() + await waitFor(() => { + expect(result.current.session).toBeNull() + }) }) it('handles commit errors', async () => { @@ -155,14 +176,26 @@ describe('useImport', () => { const { result } = renderHook(() => useImport()) - await result.current.upload('test') + await act(async () => { + await result.current.upload('test') + }) await waitFor(() => { expect(result.current.session).toEqual(mockSession) }) - await expect(result.current.commit({})).rejects.toThrow('Commit failed') + let threw = false + await act(async () => { + try { + await result.current.commit({}) + } catch { + threw = true + } + }) + expect(threw).toBe(true) - expect(result.current.error).toBe('Commit failed') + await waitFor(() => { + expect(result.current.error).toBe('Commit failed') + }) }) }) diff --git a/frontend/src/hooks/__tests__/useProxyHosts.test.ts b/frontend/src/hooks/__tests__/useProxyHosts.test.ts index 893733c7..88fad37d 100644 --- a/frontend/src/hooks/__tests__/useProxyHosts.test.ts +++ b/frontend/src/hooks/__tests__/useProxyHosts.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import { renderHook, waitFor } from '@testing-library/react' +import { renderHook, waitFor, act } from '@testing-library/react' import { useProxyHosts } from '../useProxyHosts' import * as api from '../../services/api' @@ -72,7 +72,9 @@ describe('useProxyHosts', () => { expect(result.current.loading).toBe(false) }) - await result.current.createHost(newHost) + await act(async () => { + await result.current.createHost(newHost) + }) expect(api.proxyHostsAPI.create).toHaveBeenCalledWith(newHost) await waitFor(() => { @@ -93,7 +95,9 @@ describe('useProxyHosts', () => { expect(result.current.loading).toBe(false) }) - await result.current.updateHost('1', { domain_names: 'updated.com' }) + await act(async () => { + await result.current.updateHost('1', { domain_names: 'updated.com' }) + }) expect(api.proxyHostsAPI.update).toHaveBeenCalledWith('1', { domain_names: 'updated.com' }) await waitFor(() => { @@ -115,7 +119,9 @@ describe('useProxyHosts', () => { expect(result.current.loading).toBe(false) }) - await result.current.deleteHost('1') + await act(async () => { + await result.current.deleteHost('1') + }) expect(api.proxyHostsAPI.delete).toHaveBeenCalledWith('1') await waitFor(() => { diff --git a/frontend/src/hooks/__tests__/useRemoteServers.test.ts b/frontend/src/hooks/__tests__/useRemoteServers.test.ts index 4a917831..8d073673 100644 --- a/frontend/src/hooks/__tests__/useRemoteServers.test.ts +++ b/frontend/src/hooks/__tests__/useRemoteServers.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import { renderHook, waitFor } from '@testing-library/react' +import { renderHook, waitFor, act } from '@testing-library/react' import { useRemoteServers } from '../useRemoteServers' import * as api from '../../services/api' @@ -96,7 +96,9 @@ describe('useRemoteServers', () => { expect(result.current.loading).toBe(false) }) - await result.current.createServer(newServer) + await act(async () => { + await result.current.createServer(newServer) + }) expect(api.remoteServersAPI.create).toHaveBeenCalledWith(newServer) await waitFor(() => { @@ -117,7 +119,9 @@ describe('useRemoteServers', () => { expect(result.current.loading).toBe(false) }) - await result.current.updateServer('1', { name: 'Updated Server' }) + await act(async () => { + await result.current.updateServer('1', { name: 'Updated Server' }) + }) expect(api.remoteServersAPI.update).toHaveBeenCalledWith('1', { name: 'Updated Server' }) await waitFor(() => { @@ -139,7 +143,9 @@ describe('useRemoteServers', () => { expect(result.current.loading).toBe(false) }) - await result.current.deleteServer('1') + await act(async () => { + await result.current.deleteServer('1') + }) expect(api.remoteServersAPI.delete).toHaveBeenCalledWith('1') await waitFor(() => {