14 KiB
Executable File
Backend Coverage Recovery Plan
Status: 🔴 CRITICAL - Coverage at 84.9% (Threshold: 85%) Created: 2026-01-26 Priority: IMMEDIATE
Executive Summary
Root Cause Analysis
Backend coverage dropped to 84.9% (0.1% below threshold) due to:
- cmd/seed package: 68.2% coverage (295 lines, main function hard to test)
- services package: 82.4% average (73 functions below 85% threshold)
- utils package: 74.2% coverage
- builtin DNS providers: 30.4% coverage (test coverage gap)
Impact Assessment
- Severity: Low (0.1% below threshold, ~10-15 uncovered statements)
- Cause: Recent development branch merge brought in new features:
Fastest Path to 85%
Option A (RECOMMENDED): Target 10 critical service functions → 85.2% in 1-2 hours Option B: Add cmd/seed integration tests → 85.5% in 3-4 hours Option C: Comprehensive service coverage → 86%+ in 4-6 hours
Option A: Surgical Service Function Coverage (FASTEST)
Strategy
Target the top 10 lowest-coverage service functions that are:
- Actually executed in production (not just error paths)
- Easy to test (no complex mocking)
- High statement count (max coverage gain per test)
Target Functions (Prioritized by Impact)
Phase 1: Critical Service Functions (30-45 min)
-
access_list_service.go:103 - GetByID (83.3% → 100%)
// Add test: TestAccessListService_GetByID_NotFound // Add test: TestAccessListService_GetByID_SuccessLines: 8 statements | Effort: 15 min | Gain: +0.05%
-
access_list_service.go:115 - GetByUUID (83.3% → 100%)
// Add test: TestAccessListService_GetByUUID_NotFound // Add test: TestAccessListService_GetByUUID_SuccessLines: 8 statements | Effort: 15 min | Gain: +0.05%
-
auth_service.go:30 - Register (83.3% → 100%)
// Add test: TestAuthService_Register_ValidationError // Add test: TestAuthService_Register_DuplicateEmailLines: 8 statements | Effort: 15 min | Gain: +0.05%
Phase 2: Medium Impact Functions (30-45 min)
-
backup_service.go:217 - addToZip (76.9% → 95%)
// Add test: TestBackupService_AddToZip_FileError // Add test: TestBackupService_AddToZip_SuccessLines: 7 statements | Effort: 20 min | Gain: +0.04%
-
backup_service.go:304 - unzip (71.0% → 95%)
// Add test: TestBackupService_Unzip_InvalidZip // Add test: TestBackupService_Unzip_PathTraversalLines: 7 statements | Effort: 20 min | Gain: +0.04%
-
certificate_service.go:49 - NewCertificateService (0% → 100%)
// Add test: TestNewCertificateService_InitializationLines: 8 statements | Effort: 10 min | Gain: +0.05%
Phase 3: Quick Wins (20-30 min)
-
access_list_service.go:233 - testGeoIP (9.1% → 90%)
// Add test: TestAccessList_TestGeoIP_AllowedCountry // Add test: TestAccessList_TestGeoIP_BlockedCountryLines: 9 statements | Effort: 15 min | Gain: +0.05%
-
backup_service.go:363 - GetAvailableSpace (78.6% → 100%)
// Add test: TestBackupService_GetAvailableSpace_ErrorLines: 7 statements | Effort: 10 min | Gain: +0.04%
-
access_list_service.go:127 - List (75.0% → 95%)
// Add test: TestAccessListService_List_PaginationLines: 7 statements | Effort: 10 min | Gain: +0.04%
-
access_list_service.go:159 - Delete (71.8% → 95%)
// Add test: TestAccessListService_Delete_NotFoundLines: 8 statements | Effort: 10 min | Gain: +0.05%
Total Impact: Option A
- Coverage Gain: +0.46% (84.9% → 85.36%)
- Total Time: 1h 45min - 2h 30min
- Tests Added: ~15-18 test cases
- Files Modified: 4-5 test files
Success Criteria: Backend coverage ≥ 85.2%
Option B: cmd/seed Integration Tests (MODERATE)
Strategy
Add integration-style tests for the seed command to cover the main function logic.
Implementation
File: backend/cmd/seed/main_integration_test.go
//go:build integration
package main
import (
"os"
"testing"
"path/filepath"
)
func TestSeedCommand_FullExecution(t *testing.T) {
// Setup temp database
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "test.db")
// Set environment
os.Setenv("CHARON_DB_PATH", dbPath)
defer os.Unsetenv("CHARON_DB_PATH")
// Run seed (need to refactor main() into runSeed() first)
// Test that all seed data is created
}
func TestLogSeedResult_AllCases(t *testing.T) {
// Test success case
// Test error case
// Test already exists case
}
Refactoring Required
// main.go - Extract testable function
func runSeed(dbPath string) error {
// Move main() logic here
// Return error instead of log.Fatal
}
func main() {
if err := runSeed("./data/charon.db"); err != nil {
log.Fatal(err)
}
}
Total Impact: Option B
- Coverage Gain: +0.6% (84.9% → 85.5%)
- Total Time: 3-4 hours (includes refactoring)
- Tests Added: 3-5 integration tests
- Files Modified: 2 files (main.go + main_integration_test.go)
- Risk: Medium (requires refactoring production code)
Option C: Comprehensive Service Coverage (THOROUGH)
Strategy
Systematically increase all service package functions to ≥85% coverage.
Scope
- 73 functions currently below 85%
- Average coverage increase: 10-15% per function
- Focus on:
- Error path coverage
- Edge case handling
- Validation logic
Total Impact: Option C
- Coverage Gain: +1.1% (84.9% → 86.0%)
- Total Time: 6-8 hours
- Tests Added: 80-100 test cases
- Files Modified: 15-20 test files
Recommendation: Option A
Rationale
- Fastest to 85%: 1h 45min - 2h 30min
- Low Risk: No production code changes
- High ROI: 0.46% coverage gain with minimal tests
- Debuggable: Small, focused changes easy to review
- Maintainable: Tests follow existing patterns
Implementation Order
# Phase 1: Critical Functions (30-45 min)
1. backend/internal/services/access_list_service_test.go
- Add GetByID tests
- Add GetByUUID tests
2. backend/internal/services/auth_service_test.go
- Add Register validation tests
# Phase 2: Medium Impact (30-45 min)
3. backend/internal/services/backup_service_test.go
- Add addToZip tests
- Add unzip tests
4. backend/internal/services/certificate_service_test.go
- Add NewCertificateService test
# Phase 3: Quick Wins (20-30 min)
5. backend/internal/services/access_list_service_test.go
- Add testGeoIP tests
- Add List pagination test
- Add Delete NotFound test
6. backend/internal/services/backup_service_test.go
- Add GetAvailableSpace test
# Validation (10 min)
7. Run: .github/skills/scripts/skill-runner.sh test-backend-coverage
8. Verify: Coverage ≥ 85.2%
9. Commit and push
E2E ACL Fix Plan (Separate Issue)
Current State
- global-setup.ts already has
emergencySecurityReset() - docker-compose.e2e.yml has
CHARON_EMERGENCY_TOKENset - Tests should NOT be blocked by ACL
Issue Diagnosis
The emergency reset is working, but:
- Some tests may be enabling ACL during execution
- Cleanup may not be running if test crashes
- Emergency token may need verification
Fix Strategy (15-20 min)
// tests/global-setup.ts - Enhance emergency reset
async function emergencySecurityReset(requestContext: APIRequestContext): Promise<void> {
console.log('🚨 Emergency security reset...');
// Try with emergency token header first
const emergencyToken = process.env.CHARON_EMERGENCY_TOKEN || 'test-emergency-token-for-e2e-32chars';
const modules = [
{ key: 'security.acl.enabled', value: 'false' },
{ key: 'security.waf.enabled', value: 'false' },
{ key: 'security.crowdsec.enabled', value: 'false' },
{ key: 'security.rate_limit.enabled', value: 'false' },
{ key: 'feature.cerberus.enabled', value: 'false' },
];
for (const { key, value } of modules) {
try {
// Try with emergency token
await requestContext.post('/api/v1/settings', {
data: { key, value },
headers: { 'X-Emergency-Token': emergencyToken },
});
console.log(` ✓ Disabled: ${key}`);
} catch (e) {
// Try without token (for backwards compatibility)
try {
await requestContext.post('/api/v1/settings', { data: { key, value } });
console.log(` ✓ Disabled: ${key} (no token)`);
} catch (e2) {
console.log(` ⚠ Could not disable ${key}: ${e2}`);
}
}
}
}
Verification Steps
- Test emergency reset: Run E2E tests with ACL enabled manually
- Check token: Verify emergency token is being passed correctly
- Add debug logs: Confirm reset is executing before tests
Estimated Time: 15-20 minutes
Frontend Plugins Test Decision
Current State
- Working:
__tests__/Plugins.test.tsx(312 lines, 18 tests) - Skip:
Plugins.test.tsx.skip(710 lines, 34 tests) - Coverage: Plugins.tsx @ 56.6% (working tests)
Analysis
| Metric | Working Tests | Skip File | Delta |
|---|---|---|---|
| Lines of Code | 312 | 710 | +398 (128% more) |
| Test Count | 18 | 34 | +16 (89% more) |
| Current Coverage | 56.6% | Unknown | ? |
| Mocking Complexity | Low | High | Complex setup |
Recommendation: KEEP WORKING TESTS
Rationale:
- Coverage Gain Unknown: Skip file may only add 5-10% coverage (20-30 statements)
- High Risk: 710 lines of complex mocking to debug (1-2 hours minimum)
- Diminishing Returns: 18 tests already cover critical paths
- Frontend Plan Exists: Current plan targets 86.5% without Plugins fixes
Alternative: Hybrid Approach (If Needed)
If frontend falls short of 86.5% after current plan:
- Extract 5-6 tests from skip file (highest value, lowest mock complexity)
- Focus on: Error path coverage, edge cases
- Estimated Gain: +3-5% coverage on Plugins.tsx
- Time: 30-45 minutes
Recommendation: Only pursue if frontend coverage < 85.5% after Phase 3
Complete Implementation Timeline
Phase 1: Backend Critical Functions (45 min)
- access_list_service: GetByID, GetByUUID (30 min)
- auth_service: Register validation (15 min)
- Checkpoint: Run tests, verify +0.15%
Phase 2: Backend Medium Impact (45 min)
- backup_service: addToZip, unzip (40 min)
- certificate_service: NewCertificateService (5 min)
- Checkpoint: Run tests, verify +0.13%
Phase 3: Backend Quick Wins (30 min)
- access_list_service: testGeoIP, List, Delete (20 min)
- backup_service: GetAvailableSpace (10 min)
- Checkpoint: Run tests, verify +0.18%
Phase 4: E2E Fix (20 min)
- Enhance emergency reset with token support (15 min)
- Verify with manual ACL test (5 min)
Phase 5: Validation & CI (15 min)
- Run full backend test suite with coverage
- Verify coverage ≥ 85.2%
- Commit and push
- Monitor CI for green build
Total Timeline: 2h 35min
Breakdown:
- Backend tests: 2h 0min
- E2E fix: 20 min
- Validation: 15 min
Success Criteria & DoD
Backend Coverage
- Overall coverage ≥ 85.2%
- All service functions in target list ≥ 85%
- No new coverage regressions
- All tests pass with zero failures
E2E Tests
- Emergency reset executes successfully
- No ACL blocking issues during test runs
- All E2E tests pass (chromium)
CI/CD
- Backend coverage check passes (≥85%)
- Frontend coverage check passes (≥85%)
- E2E tests pass
- All linting passes
- Security scans pass
Risk Assessment
Low Risk
- Service test additions: Following existing patterns
- Test-only changes: No production code modified
- Emergency reset enhancement: Backwards compatible
Medium Risk
- cmd/seed refactoring (Option B only): Requires production code changes
Mitigation
- Start with Option A (low risk, fast)
- Only pursue Option B/C if Option A insufficient
- Run tests after each phase (fail fast)
Appendix: Coverage Analysis Details
Current Backend Test Statistics
Test Files: 215
Source Files: 164
Test:Source Ratio: 1.31:1 ✅ (healthy)
Total Coverage: 84.9%
Package Breakdown
| Package | Coverage | Status | Priority |
|---|---|---|---|
| handlers | 85.7% | ✅ Pass | - |
| routes | 87.5% | ✅ Pass | - |
| middleware | 99.1% | ✅ Pass | - |
| services | 82.4% | ⚠️ Fail | HIGH |
| utils | 74.2% | ⚠️ Fail | MEDIUM |
| cmd/seed | 68.2% | ⚠️ Fail | LOW |
| builtin | 30.4% | ⚠️ Fail | MEDIUM |
| caddy | 97.8% | ✅ Pass | - |
| cerberus | 83.8% | ⚠️ Borderline | LOW |
| crowdsec | 85.2% | ✅ Pass | - |
| database | 91.3% | ✅ Pass | - |
| models | 96.8% | ✅ Pass | - |
Weighted Coverage Calculation
Total Statements: ~15,000
Covered Statements: ~12,735
Uncovered Statements: ~2,265
To reach 85%: Need +15 statements covered (0.1% gap)
To reach 86%: Need +165 statements covered (1.1% gap)
Next Actions
Immediate (You):
- Review and approve this plan
- Choose option (A recommended)
- Authorize implementation start
Implementation (Agent):
- Execute Plan Option A (Phases 1-3)
- Execute E2E fix
- Validate and commit
- Monitor CI
Timeline: Start → Finish = 2h 35min