- Implemented comprehensive tests for security toggle handlers in `security_toggles_test.go`, covering enable/disable functionality for ACL, WAF, Cerberus, CrowdSec, and RateLimit. - Added sample JSON response for CrowdSec decisions in `lapi_decisions_response.json`. - Created aggressive preset configuration for CrowdSec in `preset_aggressive.json`. - Documented backend coverage, security fixes, and E2E testing improvements in `2026-02-02_backend_coverage_security_fix.md`. - Developed a detailed backend test coverage restoration plan in `current_spec.md` to address existing gaps and improve overall test coverage to 86%+.
28 KiB
Backend Test Coverage Restoration Plan
Executive Summary
Objective: Restore backend test coverage from current 59.33% patch coverage to 86% locally (85%+ in CI) with 100% patch coverage (Codecov requirement).
Current State:
- Patch Coverage: 59.33% (98 lines missing coverage)
- 10 files identified with coverage gaps
- Priority ranking based on missing lines and business impact
Strategy: Systematic file-by-file coverage remediation using table-driven tests, interface mocking, and existing test infrastructure patterns.
1. Root Cause Analysis
Why Coverage Dropped
-
Recent Feature Additions Without Tests
- Caddyfile import enhancements (multi-file, normalization)
- CrowdSec hub presets and console enrollment
- Manual challenge DNS provider flow
- Feature flag batch optimization
-
Complex Error Paths Untested
- SSRF validation failures
- Path traversal edge cases
- Process lifecycle edge cases (PID recycling)
- Cache TTL expiry and eviction paths
-
Integration-Heavy Code
- External command execution (Caddy, CrowdSec)
- HTTP client operations with fallback logic
- File system operations with safety checks
- Cron scheduler lifecycle
Coverage Gap Breakdown
| File | Coverage | Missing | Partials | Priority |
|---|---|---|---|---|
| import_handler.go | 45.83% | 33 lines | 6 branches | 🔴 CRITICAL |
| crowdsec_handler.go | 21.42% | 5 lines | 6 branches | 🔴 CRITICAL |
| backup_service.go | 63.33% | 5 lines | 6 branches | 🟡 HIGH |
| hub_sync.go | 46.66% | 3 lines | 5 branches | 🟡 HIGH |
| feature_flags_handler.go | 83.33% | 4 lines | 2 branches | 🟢 MEDIUM |
| importer.go | 76.0% | 22 lines | 4 branches | 🟡 HIGH |
| security_service.go | 50.0% | 12 lines | 8 branches | 🟡 HIGH |
| hub_cache.go | 28.57% | 5 lines | 0 branches | 🟡 HIGH |
| manual_challenge_handler.go | 33.33% | 8 lines | 4 branches | 🟡 HIGH |
| crowdsec_exec.go | 0% | 1 line | 0 branches | 🟢 LOW |
Total Impact: 98 missing lines, 35 partial branches
2. File-by-File Coverage Plan
File 1: backend/internal/api/handlers/import_handler.go (933 lines)
Current Coverage: 45.83% (33 missing, 6 partials) Target Coverage: 100% Complexity: 🔴 HIGH (complex file handling, path safety, session management)
Existing Test File: backend/internal/api/handlers/import_handler_test.go
Functions Requiring Coverage
-
Upload() - Lines 85-150
- Missing: Normalization success/failure paths
- Missing: Path traversal validation
- Partials: Error handling branches
-
UploadMulti() - Lines 155-220
- Missing: Multi-file conflict detection
- Missing: Archive extraction edge cases
- Partials: File validation failures
-
Commit() - Lines 225-290
- Missing: Transient session promotion
- Missing: Import session cleanup
- Partials: ProxyHost service failures
-
DetectImports() - Lines 320-380
- Missing: Empty Caddyfile handling
- Missing: Parse failure recovery
- Partials: Conflict resolution logic
-
safeJoin() helper - Lines 450-475
- Missing: Parent directory traversal
- Missing: Absolute path rejection
Test Cases Required
// TestUpload_NormalizationSuccess
// - Upload single-line Caddyfile
// - Verify NormalizeCaddyfile called
// - Assert formatted content stored
// TestUpload_NormalizationFailure
// - Mock importer.NormalizeCaddyfile to return error
// - Verify upload fails with clear error message
// TestUpload_PathTraversal
// - Upload Caddyfile with "../../../etc/passwd"
// - Verify rejection with security error
// TestUploadMulti_ConflictDetection
// - Upload archive with duplicate domains
// - Verify conflicts reported in preview
// TestCommit_TransientPromotion
// - Create transient session
// - Commit session
// - Verify DB persistence
// TestDetectImports_EmptyCaddyfile
// - Upload empty file
// - Verify graceful handling with no imports
// TestSafeJoin_Traversal
// - Call safeJoin with "../..", "sensitive.file"
// - Verify error returned
// TestSafeJoin_AbsolutePath
// - Call safeJoin with "/etc/passwd"
// - Verify error returned
Implementation Strategy
-
Mock Importer Interface (NEW)
- Create
ImporterInterfacewithNormalizeCaddyfile(),ParseCaddyfile(),ExtractHosts() - Update handler to accept interface instead of concrete type
- Use in tests to simulate failures
- Create
-
Table-Driven Path Safety Tests
- Test matrix of forbidden patterns (
.., absolute paths, unicode tricks)
- Test matrix of forbidden patterns (
-
Session Lifecycle Tests
- Test transient → DB promotion
- Test cleanup on timeout
- Test concurrent access
Estimated Effort: 3 developer-days
File 2: backend/internal/api/handlers/crowdsec_handler.go (1006 lines)
Current Coverage: 21.42% (5 missing, 6 partials) Target Coverage: 100% Complexity: 🔴 HIGH (process lifecycle, LAPI integration, hub operations)
Existing Test File: backend/internal/api/handlers/crowdsec_handler_test.go
Functions Requiring Coverage
-
Start() - Lines 150-200
- Missing: Process already running check
- Partials: Executor.Start() failure paths
-
Stop() - Lines 205-250
- Missing: Clean stop when already stopped
- Partials: Signal failure recovery
-
ImportConfig() - Lines 350-420
- Missing: Archive extraction failures
- Missing: Backup rollback on error
- Partials: Path validation edge cases
-
ExportConfig() - Lines 425-480
- Missing: Archive creation failures
- Partials: File read errors
-
ApplyPreset() - Lines 600-680
- Missing: Hub cache miss handling
- Missing: CommandExecutor failure recovery
- Partials: Rollback on partial apply
-
ConsoleEnroll() - Lines 750-850
- Missing: LAPI endpoint validation
- Missing: Enrollment failure retries
- Partials: Token parsing errors
Test Cases Required
// TestStart_AlreadyRunning
// - Mock Status to return running=true
// - Call Start
// - Verify error "already running"
// TestStop_IdempotentStop
// - Stop when already stopped
// - Verify no error returned
// TestImportConfig_ExtractionFailure
// - Mock tar.gz extraction to fail
// - Verify rollback executed
// - Verify original config restored
// TestApplyPreset_CacheMiss
// - Mock HubCache.Load to return ErrCacheMiss
// - Verify graceful error message
// TestConsoleEnroll_InvalidEndpoint
// - Provide malformed LAPI URL
// - Verify rejection with validation error
// TestConsoleEnroll_TokenParseError
// - Mock HTTP response with invalid JSON
// - Verify clear error to user
Implementation Strategy
-
Enhance Existing Mocks
- Add failure modes to
mockCrowdsecExecutor - Add cache miss scenarios to
mockHubService
- Add failure modes to
-
Process Lifecycle Matrix
- Test all state transitions: stopped→started, started→stopped, stopped→stopped
-
LAPI Integration Tests
- Mock HTTP responses for enrollment, bans, decisions
- Test timeout and retry logic
Estimated Effort: 2.5 developer-days
File 3: backend/internal/services/backup_service.go (426 lines)
Current Coverage: 63.33% (5 missing, 6 partials) Target Coverage: 100% Complexity: 🟡 MEDIUM (cron scheduling, zip operations)
Existing Test Files:
backend/internal/services/backup_service_test.gobackend/internal/services/backup_service_disk_test.go
Functions Requiring Coverage
-
Start() - Lines 80-120
- Missing: Cron schedule parsing failures
- Missing: Duplicate start prevention
-
Stop() - Lines 125-145
- Missing: Stop when not started (idempotent)
-
CreateBackup() - Lines 200-280
- Missing: Disk full scenario
- Partials: Zip corruption recovery
-
RestoreBackup() - Lines 350-420
- Missing: Zip decompression bomb detection
- Partials: Malformed archive handling
-
GetAvailableSpace() - Lines 450-475
- Missing: Syscall failure handling
Test Cases Required
// TestStart_InvalidCronSchedule
// - Create service with invalid cron expression
// - Call Start()
// - Verify error returned
// TestStop_Idempotent
// - Stop without calling Start first
// - Verify no panic, no error
// TestCreateBackup_DiskFull
// - Mock syscall.Statfs to return 0 available space
// - Verify backup fails with "disk full" error
// TestRestoreBackup_DecompressionBomb
// - Create zip with 1GB compressed → 10TB uncompressed
// - Verify rejection before extraction
// TestGetAvailableSpace_SyscallFailure
// - Mock Statfs to return error
// - Verify graceful error handling
Implementation Strategy
-
Cron Lifecycle Tests
- Test start/stop multiple times
- Verify scheduler cleanup on stop
-
Disk Space Mocking
- Use interface for syscall operations (new)
- Mock Statfs for edge cases
-
Archive Safety Tests
- Test zip bomb detection
- Test path traversal in archive entries
Estimated Effort: 1.5 developer-days
File 4: backend/internal/crowdsec/hub_sync.go (916 lines)
Current Coverage: 46.66% (3 missing, 5 partials) Target Coverage: 100% Complexity: 🟡 MEDIUM (HTTP client, cache, tar.gz extraction)
Existing Test Files:
backend/internal/crowdsec/hub_sync_test.gobackend/internal/crowdsec/hub_pull_apply_test.gobackend/internal/crowdsec/hub_sync_raw_index_test.go
Functions Requiring Coverage
-
FetchIndex() - Lines 120-180
- Missing: HTTP timeout handling
- Partials: JSON parse failures
-
Pull() - Lines 250-350
- Missing: Etag cache hit logic
- Partials: Fallback URL failures
-
Apply() - Lines 400-480
- Missing: Tar extraction path traversal
- Missing: Rollback on partial apply
- Partials: CommandExecutor failures
-
validateHubURL() - Lines 600-650
- Missing: SSRF protection (private IP ranges)
- Missing: Invalid scheme rejection
Test Cases Required
// TestFetchIndex_HTTPTimeout
// - Mock HTTP client to timeout
// - Verify graceful error with retry suggestion
// TestPull_EtagCacheHit
// - Mock HTTP response with matching Etag
// - Verify 304 Not Modified handling
// TestApply_PathTraversal
// - Create tar.gz with "../../../etc/passwd"
// - Verify extraction blocked
// TestValidateHubURL_PrivateIP
// - Call validateHubURL("http://192.168.1.1/preset")
// - Verify SSRF protection rejects
// TestValidateHubURL_InvalidScheme
// - Call validateHubURL("ftp://example.com/preset")
// - Verify only http/https allowed
Implementation Strategy
-
HTTP Client Mocking
- Use existing
mockHTTPClientpatterns - Add timeout and redirect scenarios
- Use existing
-
SSRF Protection Tests
- Test all RFC1918 ranges (10.x, 172.16.x, 192.168.x)
- Test localhost, link-local (169.254.x)
-
Archive Safety Matrix
- Test absolute paths, symlinks, path traversal in tar entries
Estimated Effort: 2 developer-days
File 5: backend/internal/api/handlers/feature_flags_handler.go (128 lines)
Current Coverage: 83.33% (4 missing, 2 partials) Target Coverage: 100% Complexity: 🟢 LOW (simple CRUD with batch optimization)
Existing Test Files:
backend/internal/api/handlers/feature_flags_handler_test.gobackend/internal/api/handlers/feature_flags_handler_coverage_test.go
Functions Requiring Coverage
-
GetFlags() - Lines 50-80
- Missing: DB query failure handling
-
UpdateFlags() - Lines 85-120
- Partials: Transaction rollback paths
Test Cases Required
// TestGetFlags_DatabaseError
// - Mock db.Find() to return error
// - Verify 500 response with error code
// TestUpdateFlags_TransactionRollback
// - Mock transaction to fail mid-update
// - Verify rollback executed
// - Verify no partial updates persisted
Implementation Strategy
- Database Error Scenarios
- Use existing testDB patterns
- Inject failures at specific query points
Estimated Effort: 0.5 developer-days
File 6: backend/internal/caddy/importer.go (438 lines)
Current Coverage: 76.0% (22 missing, 4 partials) Target Coverage: 100% Complexity: 🟡 MEDIUM (Caddyfile parsing, JSON extraction)
Existing Test Files:
backend/internal/caddy/importer_test.gobackend/internal/caddy/importer_subroute_test.gobackend/internal/caddy/importer_additional_test.gobackend/internal/caddy/importer_extra_test.go
Functions Requiring Coverage
-
NormalizeCaddyfile() - Lines 115-165
- Missing: Temp file write failures
- Missing: caddy fmt timeout handling
-
ParseCaddyfile() - Lines 170-210
- Missing: Path traversal validation
- Missing: caddy adapt failures
-
extractHandlers() - Lines 280-350
- Missing: Deep subroute nesting
-
BackupCaddyfile() - Lines 400-435
- Missing: Path validation errors
Test Cases Required
// TestNormalizeCaddyfile_TempFileFailure
// - Mock os.CreateTemp to return error
// - Verify error propagated
// TestNormalizeCaddyfile_Timeout
// - Mock executor to timeout
// - Verify timeout error message
// TestParseCaddyfile_PathTraversal
// - Call with path "../../../etc/Caddyfile"
// - Verify rejection
// TestExtractHandlers_DeepNesting
// - Parse JSON with subroute → subroute → handler
// - Verify correct flattening
// TestBackupCaddyfile_PathTraversal
// - Call with originalPath="../../../sensitive"
// - Verify rejection
Implementation Strategy
-
Executor Mocking
- Use existing
Executorinterface - Add timeout simulation
- Use existing
-
Path Safety Test Matrix
- Comprehensive traversal patterns
- Unicode normalization tricks
Estimated Effort: 1.5 developer-days
File 7: backend/internal/services/security_service.go (442 lines)
Current Coverage: 50.0% (12 missing, 8 partials) Target Coverage: 100% Complexity: 🟡 MEDIUM (audit logging, goroutine lifecycle, break-glass tokens)
Existing Test File: backend/internal/services/security_service_test.go
Functions Requiring Coverage
-
Close() - Lines 40-55
- Missing: Double-close prevention
-
Flush() - Lines 60-75
- Missing: Timeout on slow audit writes
-
Get() - Lines 80-110
- Missing: Fallback to first row (backward compat)
- Partials: RecordNotFound handling
-
Upsert() - Lines 115-180
- Missing: Invalid CrowdSec mode rejection
- Partials: CIDR validation failures
-
processAuditEvents() - Lines 300-350
- Missing: Channel close handling
- Missing: DB error during audit write
-
ListAuditLogs() - Lines 380-430
- Partials: Pagination edge cases
Test Cases Required
// TestClose_DoubleClose
// - Call Close() twice
// - Verify no panic, idempotent
// TestFlush_SlowWrite
// - Mock DB write to delay
// - Verify Flush waits correctly
// TestGet_BackwardCompatFallback
// - DB with no "default" row but has other rows
// - Verify fallback to first row
// TestUpsert_InvalidCrowdSecMode
// - Call with CrowdSecMode="remote"
// - Verify rejection error
// TestProcessAuditEvents_ChannelClosed
// - Close channel while processing
// - Verify graceful shutdown
// TestListAuditLogs_EmptyResult
// - Query with no matching logs
// - Verify empty array, not error
Implementation Strategy
-
Goroutine Testing
- Use sync.WaitGroup to verify cleanup
- Test channel closure scenarios
-
CIDR Validation Matrix
- Test valid: "192.168.1.0/24", "10.0.0.1"
- Test invalid: "999.999.999.999/99", "not-an-ip"
Estimated Effort: 2 developer-days
File 8: backend/internal/crowdsec/hub_cache.go (234 lines)
Current Coverage: 28.57% (5 missing, 0 partials) Target Coverage: 100% Complexity: 🟢 LOW (cache CRUD with TTL)
Existing Test File: backend/internal/crowdsec/hub_cache_test.go
Functions Requiring Coverage
-
Store() - Lines 60-105
- Missing: Context cancellation handling
- Missing: Metadata write failure
-
Load() - Lines 110-145
- Missing: Expired entry handling
-
Touch() - Lines 200-220
- Missing: Update timestamp for TTL extension
-
Size() - Lines 225-240
- Missing: Total cache size calculation
Test Cases Required
// TestStore_ContextCancelled
// - Cancel context before Store completes
// - Verify operation aborted
// TestLoad_Expired
// - Store with short TTL
// - Wait for expiry
// - Verify ErrCacheExpired returned
// TestTouch_ExtendTTL
// - Store entry with 1 hour TTL
// - Touch after 30 minutes
// - Verify TTL reset
// TestSize_MultipleEntries
// - Store 3 presets of known sizes
// - Call Size()
// - Verify accurate total
Implementation Strategy
-
TTL Testing
- Use mock time function (
nowFnfield) - Avoid time.Sleep in tests
- Use mock time function (
-
Context Testing
- Test cancellation at various points
Estimated Effort: 1 developer-day
File 9: backend/internal/api/handlers/manual_challenge_handler.go (615 lines)
Current Coverage: 33.33% (8 missing, 4 partials) Target Coverage: 100% Complexity: 🟡 MEDIUM (DNS challenge API with validation)
Existing Test File: backend/internal/api/handlers/manual_challenge_handler_test.go
Functions Requiring Coverage
-
GetChallenge() - Lines 90-160
- Missing: Provider type validation
- Partials: User authorization checks
-
VerifyChallenge() - Lines 165-240
- Missing: Challenge expired handling
- Partials: Provider ownership check
-
PollChallenge() - Lines 245-310
- Partials: Status update failures
-
CreateChallenge() - Lines 420-490
- Missing: Duplicate challenge detection
- Partials: Provider type check
Test Cases Required
// TestGetChallenge_NonManualProvider
// - Create challenge on "cloudflare" provider
// - Call GetChallenge
// - Verify 400 "invalid provider type"
// TestVerifyChallenge_Expired
// - Create expired challenge (CreatedAt - 2 hours)
// - Call VerifyChallenge
// - Verify 410 Gone response
// TestCreateChallenge_Duplicate
// - Create challenge for domain
// - Create second challenge for same domain
// - Verify 409 Conflict
// TestGetChallenge_Unauthorized
// - User A creates challenge
// - User B tries to access
// - Verify 403 Forbidden
Implementation Strategy
-
Mock Services
- Use existing
ManualChallengeServiceInterface - Add authorization failure scenarios
- Use existing
-
Time-Based Testing
- Mock challenge timestamps for expiry tests
Estimated Effort: 1.5 developer-days
File 10: backend/internal/api/handlers/crowdsec_exec.go (149 lines)
Current Coverage: 0% (1 line missing, 0 partials) Target Coverage: 100% Complexity: 🟢 LOW (PID process checks)
Existing Test File: backend/internal/api/handlers/crowdsec_exec_test.go
Functions Requiring Coverage
- isCrowdSecProcess() - Lines 35-45
- Missing: Non-CrowdSec process rejection
Test Cases Required
// TestIsCrowdSecProcess_ValidProcess
// - Create mock /proc/{pid}/cmdline with "crowdsec"
// - Call isCrowdSecProcess
// - Verify returns true
// TestIsCrowdSecProcess_WrongProcess
// - Create mock /proc/{pid}/cmdline with "nginx"
// - Verify returns false (PID recycling protection)
Implementation Strategy
- Mock /proc filesystem
- Use existing
procPathfield for testing - Create temp directory with fake cmdline files
- Use existing
Estimated Effort: 0.5 developer-days
3. Implementation Priority
Phase 1: Critical Files (Week 1)
Goal: Recover 50% of missing coverage
-
import_handler.go (3 days)
- Highest missing line count (33 lines)
- Business-critical import feature
-
crowdsec_handler.go (2.5 days)
- Core security module functionality
- Complex LAPI integration
Deliverable: +40 lines coverage, patch coverage → 75%
Phase 2: High-Impact Files (Week 2)
Goal: Recover 35% of missing coverage
-
hub_sync.go (2 days)
- SSRF protection critical
- CrowdSec hub operations
-
security_service.go (2 days)
- Audit logging correctness
- Break-glass token security
-
backup_service.go (1.5 days)
- Data integrity critical
Deliverable: +35 lines coverage, patch coverage → 90%
Phase 3: Remaining Files (Week 3)
Goal: Achieve 100% patch coverage
- importer.go (1.5 days)
- manual_challenge_handler.go (1.5 days)
- hub_cache.go (1 day)
- feature_flags_handler.go (0.5 days)
- crowdsec_exec.go (0.5 days)
Deliverable: 100% patch coverage, 86%+ local coverage
4. Test Strategy
Testing Principles
-
Table-Driven Tests
- Use for input validation and error paths
- Pattern:
tests := []struct{ name, input, wantErr string }
-
Interface Mocking
- Mock external dependencies (Executor, HTTPClient, DB)
- Pattern: Define
*Interfacetype, createmock*struct
-
Test Database Isolation
- Use
OpenTestDB()for in-memory SQLite - Clean up with
defer db.Close()
- Use
-
Context Testing
- Test cancellation at critical points
- Use
context.WithCancel()in tests
-
Error Injection
- Mock failures at boundaries (disk full, network timeout, DB error)
- Verify graceful degradation
Test File Organization
backend/internal/
├── api/handlers/
│ ├── import_handler.go
│ ├── import_handler_test.go # Existing
│ ├── crowdsec_handler.go
│ ├── crowdsec_handler_test.go # Existing
│ ├── manual_challenge_handler.go
│ └── manual_challenge_handler_test.go # Existing
├── services/
│ ├── backup_service.go
│ ├── backup_service_test.go # Existing
│ ├── security_service.go
│ └── security_service_test.go # Existing
└── crowdsec/
├── hub_sync.go
├── hub_sync_test.go # Existing
├── hub_cache.go
└── hub_cache_test.go # Existing
Mock Patterns
Example: Mock Importer
// NEW: Define interface in import_handler.go
type ImporterInterface interface {
NormalizeCaddyfile(content string) (string, error)
ParseCaddyfile(path string) ([]byte, error)
ExtractHosts(json []byte) (*ImportResult, error)
}
// In test file
type mockImporter struct {
normalizeErr error
parseErr error
}
func (m *mockImporter) NormalizeCaddyfile(content string) (string, error) {
if m.normalizeErr != nil {
return "", m.normalizeErr
}
return content, nil
}
Example: Table-Driven Path Safety
func TestSafeJoin_PathTraversal(t *testing.T) {
tests := []struct {
name string
base string
path string
wantErr bool
}{
{"relative safe", "/tmp", "file.txt", false},
{"parent traversal", "/tmp", "../etc/passwd", true},
{"double parent", "/tmp", "../../root", true},
{"absolute", "/tmp", "/etc/passwd", true},
{"hidden parent", "/tmp", "a/../../../etc", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := safeJoin(tt.base, tt.path)
if (err != nil) != tt.wantErr {
t.Errorf("wantErr=%v, got err=%v", tt.wantErr, err)
}
})
}
}
Coverage Validation
After each phase, run:
# Local coverage
go test ./backend/... -coverprofile=coverage.out
go tool cover -func=coverage.out | grep total
# Expected output after Phase 3:
# total: (statements) 86.0%
# Codecov patch coverage (CI)
# Expected: 100% for all modified lines
5. Definition of Done
Coverage Metrics
- Local Coverage: 86%+ (via
go test -cover) - CI Coverage: 85%+ (Codecov reports slightly lower)
- Patch Coverage: 100% (no modified line uncovered)
Code Quality
- All tests pass on first run (no flakes)
- No skipped tests (
.Skip()only for valid reasons) - No truncated test output (avoid
head,tail) - Table-driven tests for input validation
- Mock interfaces for external dependencies
Documentation
- Test file comments explain complex scenarios
- Test names follow convention:
Test<Function>_<Scenario> - Failure messages are actionable
Security
- GORM Security Scanner passes (manual stage)
- Path traversal tests in place
- SSRF protection validated
- No hardcoded credentials in tests
CI Integration
- All tests pass in CI pipeline
- Codecov patch coverage gate passes
- No new linting errors introduced
6. Continuous Validation
Pre-Commit Checks
# Run before each commit
make test-backend-coverage
# Expected output:
# ✅ Coverage: 86.2%
# ✅ All tests passed
Daily Monitoring
- Check Codecov dashboard for regression
- Review failed CI runs immediately
- Monitor test execution time (should be <5 min for all backend tests)
Weekly Review
- Analyze coverage trends (should not drop below 85%)
- Identify new untested code from PRs
- Update this plan if new files need coverage
7. Risk Mitigation
Identified Risks
-
Flaky Tests
- Mitigation: Avoid time.Sleep, use mocks for time
- Detection: Run tests 10x locally before commit
-
Mock Drift
- Mitigation: Keep mocks in sync with interfaces
- Detection: CI will fail if interface changes
-
Coverage Regression
- Mitigation: Codecov patch coverage gate (100%)
- Detection: Automated on every PR
-
Test Maintenance Burden
- Mitigation: Use table-driven tests to reduce duplication
- Detection: Review test LOC vs production LOC ratio (should be <2:1)
8. Rollout Plan
Week 1: Critical Files (10 points)
- Day 1-2: import_handler.go tests
- Day 3-4: crowdsec_handler.go tests
- Day 5: Integration testing, coverage validation
- Checkpoint: Coverage → 75%
Week 2: High-Impact Files (20 points)
- Day 1-2: hub_sync.go + security_service.go
- Day 3-4: backup_service.go
- Day 5: Integration testing, coverage validation
- Checkpoint: Coverage → 90%
Week 3: Remaining Files (20 points)
- Day 1: importer.go + manual_challenge_handler.go
- Day 2: hub_cache.go + feature_flags_handler.go + crowdsec_exec.go
- Day 3: Full regression testing
- Day 4: Documentation, PR prep
- Day 5: Code review iterations
- Checkpoint: Coverage → 86%+, patch coverage 100%
9. Success Criteria
Quantitative Metrics
| Metric | Current | Target | Status |
|---|---|---|---|
| Local Coverage | ~60% | 86%+ | ❌ |
| CI Coverage | ~58% | 85%+ | ❌ |
| Patch Coverage | 59.33% | 100% | ❌ |
| Missing Lines | 98 | 0 | ❌ |
| Partial Branches | 35 | 0 | ❌ |
Qualitative Outcomes
- All critical business logic paths tested
- Security validation (SSRF, path traversal) enforced by tests
- Error handling coverage comprehensive
- Flake-free test suite
- Fast test execution (<5 min total)
10. Post-Implementation
Maintenance
-
New Code Requirements
- All new functions must have tests
- Pre-commit hooks enforce coverage
- PR reviews check patch coverage
-
Regression Prevention
- Codecov threshold: 85% enforced
- Weekly coverage reports
- Coverage trends dashboard
-
Test Debt Tracking
- Label low-coverage files in backlog
- Quarterly coverage audits
- Test optimization sprints
Appendix A: Test Execution Commands
# Run all backend tests
go test ./backend/...
# Run with coverage
go test ./backend/... -coverprofile=coverage.out
# View coverage report in browser
go tool cover -html=coverage.out
# Run specific file's tests
go test -v ./backend/internal/api/handlers/import_handler_test.go
# Run with race detector (slower but catches concurrency bugs)
go test -race ./backend/...
# Generate coverage for CI
go test ./backend/... -coverprofile=coverage.out -covermode=atomic
Appendix B: Reference Test Files
Best examples to follow from existing codebase:
- Table-Driven:
backend/internal/caddy/importer_test.go - Mock Interfaces:
backend/internal/api/handlers/auth_handler_test.go - Test DB Usage:
backend/internal/testutil/testdb_test.go - Error Injection:
backend/internal/services/proxyhost_service_test.go - Context Testing:
backend/internal/network/safeclient_test.go
Sign-Off
Plan Version: 1.0 Created: 2025-01-XX Status: APPROVED FOR IMPLEMENTATION
Next Steps:
- Review plan with team
- Begin Phase 1 implementation
- Daily standup on progress
- Weekly coverage checkpoint reviews