From aa85c911c064447145c405db62cbce9927be0cc0 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 8 Feb 2026 00:02:09 +0000 Subject: [PATCH] chore: refactor tests to improve clarity and reliability - Removed unnecessary test.skip() calls in various test files, replacing them with comments for clarity. - Enhanced retry logic in TestDataManager for API requests to handle rate limiting more gracefully. - Updated security helper functions to include retry mechanisms for fetching security status and setting module states. - Improved loading completion checks to handle page closure scenarios. - Adjusted WebKit-specific tests to run in all browsers, removing the previous skip logic. - General cleanup and refactoring across multiple test files to enhance readability and maintainability. --- frontend/src/api/__tests__/client.test.ts | 7 +- frontend/src/api/__tests__/import.test.ts | 17 +- .../src/api/__tests__/logs-websocket.test.ts | 3 +- .../src/api/__tests__/securityHeaders.test.ts | 25 +- frontend/src/components/AccessListForm.tsx | 6 +- .../src/components/AccessListSelector.tsx | 40 +- frontend/src/components/ProxyHostForm.tsx | 244 +- .../__tests__/AccessListForm.test.tsx | 28 +- .../__tests__/AccessListSelector.test.tsx | 15 +- .../__tests__/CertificateList.test.tsx | 162 +- .../__tests__/CredentialManager.test.tsx | 138 +- .../src/components/__tests__/Layout.test.tsx | 40 +- .../__tests__/NotificationCenter.test.tsx | 21 +- .../__tests__/ProxyHostForm-dns.test.tsx | 24 + .../__tests__/ProxyHostForm.test.tsx | 193 +- ...SecurityNotificationSettingsModal.test.tsx | 18 +- frontend/src/components/ui/Tabs.test.tsx | 2 +- .../components/ui/__tests__/Input.test.tsx | 21 +- frontend/src/pages/Certificates.tsx | 1 + frontend/src/pages/CrowdSecConfig.tsx | 114 +- frontend/src/pages/ProxyHosts.tsx | 39 +- .../src/pages/__tests__/AccessLists.test.tsx | 332 + .../src/pages/__tests__/Certificates.test.tsx | 147 + .../pages/__tests__/CrowdSecConfig.test.tsx | 13 +- .../src/pages/__tests__/SMTPSettings.test.tsx | 44 +- .../src/pages/__tests__/Security.spec.tsx | 28 + .../pages/__tests__/SecurityHeaders.test.tsx | 143 +- .../src/pages/__tests__/Settings.test.tsx | 57 + frontend/src/test/setup.ts | 21 + frontend/trivy-results.json | 21869 ++++++++++++++-- playwright.config.js | 13 +- scripts/frontend-test-coverage.sh | 26 +- tests/auth.setup.ts | 91 +- tests/core/access-lists-crud.spec.ts | 12 +- tests/core/authentication.spec.ts | 4 +- tests/core/certificates.spec.ts | 93 +- tests/dns-provider-crud.spec.ts | 11 +- .../emergency-server/emergency-server.spec.ts | 8 +- .../emergency-server/tier2-validation.spec.ts | 4 +- .../caddy-import-firefox.spec.ts | 13 +- tests/fixtures/auth-fixtures.ts | 31 +- tests/modal-dropdown-triage.spec.ts | 315 + tests/monitoring/real-time-logs.spec.ts | 27 - tests/proxy-host-dropdown-fix.spec.ts | 119 + .../combined-enforcement.spec.ts | 2 +- .../emergency-reset.spec.ts | 324 +- .../emergency-token.spec.ts | 14 +- .../rate-limit-enforcement.spec.ts | 59 +- .../waf-enforcement.spec.ts | 44 +- .../zzz-admin-whitelist-blocking.spec.ts | 35 +- .../zzzz-break-glass-recovery.spec.ts | 108 +- tests/security-teardown.setup.ts | 88 +- tests/security/acl-integration.spec.ts | 61 +- .../crowdsec-console-enrollment.spec.ts | 5 +- tests/security/crowdsec-decisions.spec.ts | 46 +- tests/security/crowdsec-diagnostics.spec.ts | 36 +- tests/security/crowdsec-import.spec.ts | 8 - tests/security/rate-limiting.spec.ts | 3 - tests/security/security-dashboard.spec.ts | 39 +- tests/settings/account-settings.spec.ts | 5 +- tests/settings/encryption-management.spec.ts | 12 +- tests/settings/notifications.spec.ts | 18 +- tests/settings/smtp-settings.spec.ts | 6 +- tests/settings/system-settings.spec.ts | 15 +- tests/settings/user-management.spec.ts | 37 +- .../tasks/caddy-import-cross-browser.spec.ts | 2 +- tests/tasks/caddy-import-gaps.spec.ts | 4 +- tests/utils/TestDataManager.ts | 89 +- tests/utils/security-helpers.ts | 48 +- tests/utils/wait-helpers.ts | 14 +- .../caddy-import-webkit.spec.ts | 15 +- 71 files changed, 22475 insertions(+), 3241 deletions(-) create mode 100644 frontend/src/pages/__tests__/AccessLists.test.tsx create mode 100644 frontend/src/pages/__tests__/Certificates.test.tsx create mode 100644 frontend/src/pages/__tests__/Settings.test.tsx create mode 100644 tests/modal-dropdown-triage.spec.ts create mode 100644 tests/proxy-host-dropdown-fix.spec.ts diff --git a/frontend/src/api/__tests__/client.test.ts b/frontend/src/api/__tests__/client.test.ts index adf02f31..1b161ff9 100644 --- a/frontend/src/api/__tests__/client.test.ts +++ b/frontend/src/api/__tests__/client.test.ts @@ -130,9 +130,10 @@ describe('api client', () => { expect(handler).toBeDefined() // Call handler with auth endpoint error to verify it skips the auth error handler - if (handler) { - await handler(error) - } + const resultPromise = handler ? handler(error) : Promise.reject(new Error('handler missing')) + + await expect(resultPromise).rejects.toBe(error) + expect(onAuthError).not.toHaveBeenCalled() warnSpy.mockRestore() }) diff --git a/frontend/src/api/__tests__/import.test.ts b/frontend/src/api/__tests__/import.test.ts index 5e4fd048..11485771 100644 --- a/frontend/src/api/__tests__/import.test.ts +++ b/frontend/src/api/__tests__/import.test.ts @@ -10,6 +10,9 @@ vi.mock('../client', () => ({ })); describe('import API', () => { + const mockedGet = vi.mocked(client.get); + const mockedPost = vi.mocked(client.post); + beforeEach(() => { vi.clearAllMocks(); }); @@ -17,7 +20,7 @@ describe('import API', () => { it('uploadCaddyfile posts content', async () => { const content = 'example.com'; const mockResponse = { preview: { hosts: [] } }; - (client.post as any).mockResolvedValue({ data: mockResponse }); + mockedPost.mockResolvedValue({ data: mockResponse }); const result = await uploadCaddyfile(content); expect(client.post).toHaveBeenCalledWith('/import/upload', { content }); @@ -27,7 +30,7 @@ describe('import API', () => { it('uploadCaddyfilesMulti posts files', async () => { const files = [{ filename: 'Caddyfile', content: 'foo.com' }]; const mockResponse = { preview: { hosts: [] } }; - (client.post as any).mockResolvedValue({ data: mockResponse }); + mockedPost.mockResolvedValue({ data: mockResponse }); const result = await uploadCaddyfilesMulti(files); expect(client.post).toHaveBeenCalledWith('/import/upload-multi', { files }); @@ -36,7 +39,7 @@ describe('import API', () => { it('getImportPreview gets preview', async () => { const mockResponse = { preview: { hosts: [] } }; - (client.get as any).mockResolvedValue({ data: mockResponse }); + mockedGet.mockResolvedValue({ data: mockResponse }); const result = await getImportPreview(); expect(client.get).toHaveBeenCalledWith('/import/preview'); @@ -49,7 +52,7 @@ describe('import API', () => { const names = { 'foo.com': 'My Site' }; const mockResponse = { created: 1, updated: 0, skipped: 0, errors: [] }; - (client.post as any).mockResolvedValue({ data: mockResponse }); + mockedPost.mockResolvedValue({ data: mockResponse }); const result = await commitImport(sessionUUID, resolutions, names); expect(client.post).toHaveBeenCalledWith('/import/commit', { @@ -61,7 +64,7 @@ describe('import API', () => { }); it('cancelImport posts cancel', async () => { - (client.post as any).mockResolvedValue({}); + mockedPost.mockResolvedValue({}); await cancelImport(); expect(client.post).toHaveBeenCalledWith('/import/cancel'); @@ -69,7 +72,7 @@ describe('import API', () => { it('getImportStatus gets status', async () => { const mockResponse = { has_pending: true }; - (client.get as any).mockResolvedValue({ data: mockResponse }); + mockedGet.mockResolvedValue({ data: mockResponse }); const result = await getImportStatus(); expect(client.get).toHaveBeenCalledWith('/import/status'); @@ -77,7 +80,7 @@ describe('import API', () => { }); it('getImportStatus handles error', async () => { - (client.get as any).mockRejectedValue(new Error('Failed')); + mockedGet.mockRejectedValue(new Error('Failed')); const result = await getImportStatus(); expect(result).toEqual({ has_pending: false }); diff --git a/frontend/src/api/__tests__/logs-websocket.test.ts b/frontend/src/api/__tests__/logs-websocket.test.ts index 912db2df..1fb98906 100644 --- a/frontend/src/api/__tests__/logs-websocket.test.ts +++ b/frontend/src/api/__tests__/logs-websocket.test.ts @@ -54,8 +54,7 @@ describe('logs API - connectLiveLogs', () => { beforeEach(() => { // Mock global WebSocket mockWebSocket = new MockWebSocket(''); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (globalThis as any).WebSocket = class MockedWebSocket extends MockWebSocket { + (globalThis as typeof globalThis & { WebSocket: typeof WebSocket }).WebSocket = class MockedWebSocket extends MockWebSocket { constructor(url: string) { super(url); // eslint-disable-next-line @typescript-eslint/no-this-alias diff --git a/frontend/src/api/__tests__/securityHeaders.test.ts b/frontend/src/api/__tests__/securityHeaders.test.ts index 145aeb94..3fe6a5b8 100644 --- a/frontend/src/api/__tests__/securityHeaders.test.ts +++ b/frontend/src/api/__tests__/securityHeaders.test.ts @@ -12,13 +12,18 @@ vi.mock('../client', () => ({ })); describe('securityHeadersApi', () => { + const mockedGet = vi.mocked(client.get); + const mockedPost = vi.mocked(client.post); + const mockedPut = vi.mocked(client.put); + const mockedDelete = vi.mocked(client.delete); + beforeEach(() => { vi.clearAllMocks(); }); it('listProfiles returns profiles', async () => { const mockProfiles = [{ id: 1, name: 'Profile 1' }]; - (client.get as any).mockResolvedValue({ data: { profiles: mockProfiles } }); + mockedGet.mockResolvedValue({ data: { profiles: mockProfiles } }); const result = await securityHeadersApi.listProfiles(); expect(client.get).toHaveBeenCalledWith('/security/headers/profiles'); @@ -27,7 +32,7 @@ describe('securityHeadersApi', () => { it('getProfile returns a profile', async () => { const mockProfile = { id: 1, name: 'Profile 1' }; - (client.get as any).mockResolvedValue({ data: { profile: mockProfile } }); + mockedGet.mockResolvedValue({ data: { profile: mockProfile } }); const result = await securityHeadersApi.getProfile(1); expect(client.get).toHaveBeenCalledWith('/security/headers/profiles/1'); @@ -37,7 +42,7 @@ describe('securityHeadersApi', () => { it('createProfile creates a profile', async () => { const newProfile = { name: 'New Profile' }; const mockResponse = { id: 1, ...newProfile }; - (client.post as any).mockResolvedValue({ data: { profile: mockResponse } }); + mockedPost.mockResolvedValue({ data: { profile: mockResponse } }); const result = await securityHeadersApi.createProfile(newProfile); expect(client.post).toHaveBeenCalledWith('/security/headers/profiles', newProfile); @@ -47,7 +52,7 @@ describe('securityHeadersApi', () => { it('updateProfile updates a profile', async () => { const updates = { name: 'Updated Profile' }; const mockResponse = { id: 1, ...updates }; - (client.put as any).mockResolvedValue({ data: { profile: mockResponse } }); + mockedPut.mockResolvedValue({ data: { profile: mockResponse } }); const result = await securityHeadersApi.updateProfile(1, updates); expect(client.put).toHaveBeenCalledWith('/security/headers/profiles/1', updates); @@ -55,7 +60,7 @@ describe('securityHeadersApi', () => { }); it('deleteProfile deletes a profile', async () => { - (client.delete as any).mockResolvedValue({}); + mockedDelete.mockResolvedValue({}); await securityHeadersApi.deleteProfile(1); expect(client.delete).toHaveBeenCalledWith('/security/headers/profiles/1'); @@ -63,7 +68,7 @@ describe('securityHeadersApi', () => { it('getPresets returns presets', async () => { const mockPresets = [{ name: 'Basic' }]; - (client.get as any).mockResolvedValue({ data: { presets: mockPresets } }); + mockedGet.mockResolvedValue({ data: { presets: mockPresets } }); const result = await securityHeadersApi.getPresets(); expect(client.get).toHaveBeenCalledWith('/security/headers/presets'); @@ -73,7 +78,7 @@ describe('securityHeadersApi', () => { it('applyPreset applies a preset', async () => { const request = { preset_type: 'basic', name: 'My Preset' }; const mockResponse = { id: 1, ...request }; - (client.post as any).mockResolvedValue({ data: { profile: mockResponse } }); + mockedPost.mockResolvedValue({ data: { profile: mockResponse } }); const result = await securityHeadersApi.applyPreset(request); expect(client.post).toHaveBeenCalledWith('/security/headers/presets/apply', request); @@ -83,7 +88,7 @@ describe('securityHeadersApi', () => { it('calculateScore calculates score', async () => { const config = { hsts_enabled: true }; const mockResponse = { score: 90 }; - (client.post as any).mockResolvedValue({ data: mockResponse }); + mockedPost.mockResolvedValue({ data: mockResponse }); const result = await securityHeadersApi.calculateScore(config); expect(client.post).toHaveBeenCalledWith('/security/headers/score', config); @@ -93,7 +98,7 @@ describe('securityHeadersApi', () => { it('validateCSP validates CSP', async () => { const csp = "default-src 'self'"; const mockResponse = { valid: true, errors: [] }; - (client.post as any).mockResolvedValue({ data: mockResponse }); + mockedPost.mockResolvedValue({ data: mockResponse }); const result = await securityHeadersApi.validateCSP(csp); expect(client.post).toHaveBeenCalledWith('/security/headers/csp/validate', { csp }); @@ -103,7 +108,7 @@ describe('securityHeadersApi', () => { it('buildCSP builds CSP', async () => { const directives = [{ directive: 'default-src', values: ["'self'"] }]; const mockResponse = { csp: "default-src 'self'" }; - (client.post as any).mockResolvedValue({ data: mockResponse }); + mockedPost.mockResolvedValue({ data: mockResponse }); const result = await securityHeadersApi.buildCSP(directives); expect(client.post).toHaveBeenCalledWith('/security/headers/csp/build', { directives }); diff --git a/frontend/src/components/AccessListForm.tsx b/frontend/src/components/AccessListForm.tsx index 1612b77c..30b42c80 100644 --- a/frontend/src/components/AccessListForm.tsx +++ b/frontend/src/components/AccessListForm.tsx @@ -378,10 +378,11 @@ export function AccessListForm({ initialData, onSubmit, onCancel, onDelete, isLo
- +

Apply this access list to hosts

setFormData({ ...formData, enabled: checked })} /> @@ -393,12 +394,13 @@ export function AccessListForm({ initialData, onSubmit, onCancel, onDelete, isLo
- +

Allow only private network IPs (10.x.x.x, 192.168.x.x, 172.16-31.x.x)

setFormData({ ...formData, local_network_only: checked }) diff --git a/frontend/src/components/AccessListSelector.tsx b/frontend/src/components/AccessListSelector.tsx index 939004f4..9dcbcdd7 100644 --- a/frontend/src/components/AccessListSelector.tsx +++ b/frontend/src/components/AccessListSelector.tsx @@ -1,5 +1,12 @@ import { useAccessLists } from '../hooks/useAccessLists'; import { ExternalLink } from 'lucide-react'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from './ui/Select'; interface AccessListSelectorProps { value: number | null; @@ -13,25 +20,28 @@ export default function AccessListSelector({ value, onChange }: AccessListSelect return (
-