fix(ci): resolve E2E workflow failures and boost test coverage

E2E Workflow Fixes:

Add frontend dependency installation step (missing npm ci in frontend/)
Remove incorrect working-directory from backend build step
Update Node.js version from v18 to v20 (dependency requirements)
Backend Coverage: 84.9% → 85.0% (20+ new test functions):

Access list service validation and templates
Backup service error handling and edge cases
Security audit logs and rule sets
Auth service edge cases and token validation
Certificate service upload and sync error paths
Frontend Coverage: 85.06% → 85.66% (27 new tests):

Tabs component accessibility and keyboard navigation
Plugins page status badges and error handling
SecurityHeaders CRUD operations and presets
API wrappers for credentials and encryption endpoints
E2E Infrastructure:

Enhanced global-setup with emergency security module reset
Added retry logic and verification for settings propagation
Known Issues:

19 E2E tests still failing (ACL blocking security APIs - Issue #16)
7 Plugins modal UI tests failing (non-critical)
To be addressed in follow-up PR
Fixes #550 E2E workflow failures
Related to #16 ACL implementation
This commit is contained in:
GitHub Actions
2026-01-26 04:09:57 +00:00
parent 0b9484faf0
commit 29d2ec9cbf
20 changed files with 4280 additions and 1516 deletions

View File

@@ -0,0 +1,119 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import {
getCredentials,
getCredential,
createCredential,
updateCredential,
deleteCredential,
testCredential,
enableMultiCredentials,
type DNSProviderCredential,
type CredentialRequest,
type CredentialTestResult,
} from '../credentials'
import client from '../client'
vi.mock('../client')
const mockCredential: DNSProviderCredential = {
id: 1,
uuid: 'test-uuid-1',
dns_provider_id: 1,
label: 'Production Credentials',
zone_filter: '*.example.com',
enabled: true,
propagation_timeout: 120,
polling_interval: 2,
key_version: 1,
success_count: 5,
failure_count: 0,
created_at: '2025-01-01T00:00:00Z',
updated_at: '2025-01-01T00:00:00Z',
}
const mockCredentialRequest: CredentialRequest = {
label: 'New Credentials',
zone_filter: '*.example.com',
credentials: { api_token: 'test-token-123' },
propagation_timeout: 120,
polling_interval: 2,
enabled: true,
}
describe('credentials API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should call getCredentials with correct endpoint', async () => {
const mockData = [mockCredential, { ...mockCredential, id: 2, label: 'Secondary' }]
vi.mocked(client.get).mockResolvedValue({
data: { credentials: mockData, total: 2 },
})
const result = await getCredentials(1)
expect(client.get).toHaveBeenCalledWith('/dns-providers/1/credentials')
expect(result).toEqual(mockData)
expect(result).toHaveLength(2)
})
it('should call getCredential with correct endpoint', async () => {
vi.mocked(client.get).mockResolvedValue({ data: mockCredential })
const result = await getCredential(1, 1)
expect(client.get).toHaveBeenCalledWith('/dns-providers/1/credentials/1')
expect(result).toEqual(mockCredential)
})
it('should call createCredential with correct endpoint and data', async () => {
vi.mocked(client.post).mockResolvedValue({ data: mockCredential })
const result = await createCredential(1, mockCredentialRequest)
expect(client.post).toHaveBeenCalledWith('/dns-providers/1/credentials', mockCredentialRequest)
expect(result).toEqual(mockCredential)
})
it('should call updateCredential with correct endpoint and data', async () => {
const updatedCredential = { ...mockCredential, label: 'Updated Label' }
vi.mocked(client.put).mockResolvedValue({ data: updatedCredential })
const result = await updateCredential(1, 1, mockCredentialRequest)
expect(client.put).toHaveBeenCalledWith('/dns-providers/1/credentials/1', mockCredentialRequest)
expect(result).toEqual(updatedCredential)
})
it('should call deleteCredential with correct endpoint', async () => {
vi.mocked(client.delete).mockResolvedValue({ data: undefined })
await deleteCredential(1, 1)
expect(client.delete).toHaveBeenCalledWith('/dns-providers/1/credentials/1')
})
it('should call testCredential with correct endpoint', async () => {
const mockTestResult: CredentialTestResult = {
success: true,
message: 'Credentials validated successfully',
propagation_time_ms: 1200,
}
vi.mocked(client.post).mockResolvedValue({ data: mockTestResult })
const result = await testCredential(1, 1)
expect(client.post).toHaveBeenCalledWith('/dns-providers/1/credentials/1/test')
expect(result).toEqual(mockTestResult)
expect(result.success).toBe(true)
})
it('should call enableMultiCredentials with correct endpoint', async () => {
vi.mocked(client.post).mockResolvedValue({ data: undefined })
await enableMultiCredentials(1)
expect(client.post).toHaveBeenCalledWith('/dns-providers/1/enable-multi-credentials')
})
})

View File

@@ -0,0 +1,95 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import {
getEncryptionStatus,
rotateEncryptionKey,
getRotationHistory,
validateKeyConfiguration,
type RotationStatus,
type RotationResult,
type RotationHistoryEntry,
type KeyValidationResult,
} from '../encryption'
import client from '../client'
vi.mock('../client')
const mockRotationStatus: RotationStatus = {
current_version: 2,
next_key_configured: true,
legacy_key_count: 1,
providers_on_current_version: 5,
providers_on_older_versions: 0,
}
const mockRotationResult: RotationResult = {
total_providers: 5,
success_count: 5,
failure_count: 0,
duration: '2.5s',
new_key_version: 3,
}
const mockHistoryEntry: RotationHistoryEntry = {
id: 1,
uuid: 'test-uuid-1',
actor: 'admin@example.com',
action: 'encryption_key_rotated',
event_category: 'security',
details: 'Rotated from version 1 to version 2',
created_at: '2025-01-01T00:00:00Z',
}
const mockValidationResult: KeyValidationResult = {
valid: true,
message: 'Key configuration is valid',
}
describe('encryption API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should call getEncryptionStatus with correct endpoint', async () => {
vi.mocked(client.get).mockResolvedValue({ data: mockRotationStatus })
const result = await getEncryptionStatus()
expect(client.get).toHaveBeenCalledWith('/admin/encryption/status')
expect(result).toEqual(mockRotationStatus)
expect(result.current_version).toBe(2)
})
it('should call rotateEncryptionKey with correct endpoint', async () => {
vi.mocked(client.post).mockResolvedValue({ data: mockRotationResult })
const result = await rotateEncryptionKey()
expect(client.post).toHaveBeenCalledWith('/admin/encryption/rotate')
expect(result).toEqual(mockRotationResult)
expect(result.new_key_version).toBe(3)
expect(result.success_count).toBe(5)
})
it('should call getRotationHistory with correct endpoint', async () => {
const mockHistory = [mockHistoryEntry, { ...mockHistoryEntry, id: 2 }]
vi.mocked(client.get).mockResolvedValue({
data: { history: mockHistory, total: 2 },
})
const result = await getRotationHistory()
expect(client.get).toHaveBeenCalledWith('/admin/encryption/history')
expect(result).toEqual(mockHistory)
expect(result).toHaveLength(2)
})
it('should call validateKeyConfiguration with correct endpoint', async () => {
vi.mocked(client.post).mockResolvedValue({ data: mockValidationResult })
const result = await validateKeyConfiguration()
expect(client.post).toHaveBeenCalledWith('/admin/encryption/validate')
expect(result).toEqual(mockValidationResult)
expect(result.valid).toBe(true)
})
})