docs/TESTING.md
Rama main · réplica de lectura del repositorio original.
# Testing Documentation
**Last Updated:** 2026-04-28
**Version:** 1.2.4
## Overview
This document tracks testing progress and coverage for ActaLog, providing guidelines for writing tests and maintaining the test suite.
## Test Coverage Summary
### Overall Coverage: 81.6%
| Service | Coverage | Status |
|---------|----------|--------|
| audit_log_service.go | 100.0% | ✅ Complete |
| backup_service.go | 100.0% | ✅ Complete |
| data_change_log_service.go | 100.0% | ✅ Complete |
| email_log_service.go | 100.0% | ✅ Complete |
| movement_service.go | 100.0% | ✅ Complete |
| notification_like_service.go | 100.0% | ✅ Complete |
| subscription_service.go | 100.0% | ✅ Complete |
| user_settings_service.go | 95.2% | ✅ Complete |
| user_workout_service.go | 100.0% | ✅ Complete |
| wod_service.go | 100.0% | ✅ Complete |
| wodify_import_service.go | 100.0% | ✅ Complete |
| workout_template_service.go | 100.0% | ✅ Complete |
| workout_wod_service.go | 100.0% | ✅ Complete |
| export_service.go | 83.6% | 🔄 Good |
| notification_service.go | 82.6% | 🔄 Good |
| workout_service.go | 80.0% | 🔄 Good |
| organization_service.go | 77.8% | 🔄 Needs Improvement |
| user_service.go | 61.9% | ⚠️ Needs Improvement |
| import_service.go | 60.8% | ⚠️ Needs Improvement |
### Coverage Goals
| Component | Target | Current | Status |
|-----------|--------|---------|--------|
| Overall | >80% | 81.6% | ✅ Met |
| Service Layer | >90% | 81.6% | 🔄 Close |
| Repository Layer | >85% | - | ⏳ Pending |
| Handler Layer | >75% | - | ⏳ Pending |
## Test Infrastructure
### Directory Structure
```
internal/service/
├── test_helpers.go # Shared mock repositories (600+ lines)
├── *_service_test.go # Service unit tests
```
### Mock Repository Infrastructure
Located in `internal/service/test_helpers.go`:
**User-Related Mocks:**
- `mockUserRepo` - User CRUD with error injection support
- `mockUserSettingsRepo` - User settings management
- `mockRefreshTokenRepo` - JWT refresh token handling
**Workout Mocks:**
- `mockUserWorkoutRepo` - User workout logging with stats
- `mockUserWorkoutMovementRepo` - Movement performance tracking
- `mockUserWorkoutWODRepo` - WOD performance tracking
- `mockWorkoutRepo` - Workout template management
- `mockWorkoutMovementRepo` - Template-movement associations
- `mockWorkoutWODRepo` - Template-WOD associations
**Entity Mocks:**
- `mockMovementRepo` - Movement CRUD with category support
- `mockWODRepo` - WOD management with search
- `mockWorkoutTemplateRepo` - Template CRUD
**System Mocks:**
- `mockAuditLogRepo` - Audit trail logging
- `mockNotificationRepo` - Notification system
- `mockSubscriptionAccessRepo` - Subscription access checks
- `mockEmailService` - Email sending simulation
### Mock Pattern
All mocks use constructor functions for proper initialization:
```go
type mockUserWorkoutRepo struct {
userWorkouts map[int64]*domain.UserWorkout
nextID int64
getByIDError error
createError error
}
func newMockUserWorkoutRepo() *mockUserWorkoutRepo {
return &mockUserWorkoutRepo{
userWorkouts: make(map[int64]*domain.UserWorkout),
nextID: 1,
}
}
```
### Error Injection
Mocks support error injection for testing error paths:
```go
mockRepo := newMockUserWorkoutRepo()
mockRepo.createError = errors.New("database connection lost")
// Test will now receive this error from Create()
```
## Running Tests
### Quick Commands
```bash
# Run all tests
make test
# Run service tests only
go test -v ./internal/service/...
# Run with coverage
go test -coverprofile=coverage.out ./internal/service/...
go tool cover -html=coverage.out
# Run specific test
go test -v -run TestBackupService ./internal/service/...
# Run with race detection
go test -race ./internal/service/...
```
### Coverage Report
```bash
# Generate per-function coverage
go test -coverprofile=coverage.out ./internal/service/...
go tool cover -func=coverage.out
# Generate HTML report
go tool cover -html=coverage.out -o coverage.html
```
## Test Patterns
### Table-Driven Tests
All tests follow the table-driven pattern for comprehensive coverage:
```go
func TestService_Method(t *testing.T) {
tests := []struct {
name string
input InputType
setupMock func(*mockRepo)
expectedError error
validate func(*testing.T, ResultType)
}{
{
name: "successful operation",
input: validInput,
setupMock: func(m *mockRepo) {
m.data[1] = expectedData
},
expectedError: nil,
validate: func(t *testing.T, result ResultType) {
if result.ID != expected.ID {
t.Errorf("expected ID %d, got %d", expected.ID, result.ID)
}
},
},
{
name: "handles not found",
input: missingInput,
expectedError: service.ErrNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockRepo := newMockRepo()
if tt.setupMock != nil {
tt.setupMock(mockRepo)
}
svc := NewService(mockRepo)
result, err := svc.Method(tt.input)
if tt.expectedError != nil {
if !errors.Is(err, tt.expectedError) {
t.Errorf("expected error %v, got %v", tt.expectedError, err)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if tt.validate != nil {
tt.validate(t, result)
}
})
}
}
```
### Integration Tests with SQLite
For tests requiring a real database (backup/restore, migrations):
```go
func setupTestDB(t *testing.T) *sql.DB {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("failed to open test database: %v", err)
}
// Create schema
_, err = db.Exec(`
CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE);
CREATE TABLE movements (id INTEGER PRIMARY KEY, name TEXT);
-- ... full schema
`)
if err != nil {
t.Fatalf("failed to create schema: %v", err)
}
return db
}
```
### Arrange-Act-Assert Pattern
```go
func TestExample(t *testing.T) {
// Arrange - Set up test data and mocks
mockRepo := newMockRepo()
mockRepo.data[1] = &domain.Entity{ID: 1, Name: "test"}
service := NewService(mockRepo)
// Act - Execute the operation
result, err := service.GetByID(1)
// Assert - Verify the outcome
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result.Name != "test" {
t.Errorf("expected name 'test', got %q", result.Name)
}
}
```
## Test Coverage by Feature
### Backup Service (100% coverage)
Comprehensive tests in `backup_service_test.go`:
- `TestBackupService_CreateBackup` - Full backup creation with ZIP archive
- `TestBackupService_CreateBackup_UserNotFound` - Error handling for missing user
- `TestBackupService_CreateBackup_WithUploads` - Backup with file attachments
- `TestBackupService_RestoreBackup` - Replace mode restoration
- `TestBackupService_RestoreBackup_MergeMode` - Merge mode with updates
- `TestBackupService_RestoreBackup_SkipMode` - Skip mode preserving existing
- `TestBackupService_RestoreBackup_FileNotFound` - Missing backup file handling
- `TestBackupService_CreateSQLiteDump` - SQLite dump generation
- `TestBackupService_ExportAllTables` - JSON export of all tables
- `TestBackupService_ExportAllTables_EmptyDatabase` - Empty database handling
### User Workout Service (100% coverage)
Tests in `user_workout_service_test.go`:
- `TestUserWorkoutService_LogWorkout` - Workout logging
- `TestUserWorkoutService_LogWorkoutWithPerformance` - Logging with PR detection
- `TestUserWorkoutService_GetLoggedWorkout` - Retrieve with authorization
- `TestUserWorkoutService_UpdateLoggedWorkout` - Update with authorization
- `TestUserWorkoutService_DeleteLoggedWorkout` - Delete with authorization
- `TestUserWorkoutService_GetWorkoutStatsForMonth` - Monthly statistics
- `TestUserWorkoutService_ListByUser` - User workout listing
- `TestUserWorkoutService_GetWorkoutSummary` - Summary calculations
### Subscription Service (100% coverage)
Tests in `subscription_service_test.go`:
- Create/cancel user subscriptions
- Create/cancel organization subscriptions
- Mark subscriptions as paid
- Access checking (user-level and organization-level)
- Permanent free subscription handling
### Import/Export Services
Tests in `import_service_test.go`, `wodify_import_service_test.go`:
- CSV parsing and validation
- Duplicate detection and handling
- Skip vs update modes
- Error recovery
- Multi-format support (Wodify CSV, JSON)
## Writing New Tests
### Guidelines
1. **Test Behavior, Not Implementation**
- Focus on what the service does, not how
- Test public API only
2. **Use Descriptive Names**
- Good: `"successful workout log with PR detection"`
- Bad: `"test1"`
3. **Cover Edge Cases**
- Happy path (success scenarios)
- Error conditions (not found, unauthorized, validation)
- Boundary conditions (empty lists, nil values, limits)
4. **Keep Tests Independent**
- Each test runs in isolation
- No shared state between tests
- Fresh mock instances per test
5. **Use `errors.Is()` for Error Comparison**
```go
if !errors.Is(err, service.ErrNotFound) {
t.Errorf("expected ErrNotFound, got %v", err)
}
```
### Adding a New Mock
1. Add interface implementation to `test_helpers.go`
2. Include constructor function with map initialization
3. Add error injection fields as needed
4. Implement all interface methods
## Continuous Integration
Tests run automatically on:
- Pull request creation
- Push to main branch
CI configuration: `.github/workflows/ci.yml`
```yaml
- name: Run tests
run: go test -v -race -coverprofile=coverage.out ./...
```
## Next Steps
### High Priority
1. Improve `user_service.go` coverage (61.9% → 80%+)
2. Improve `import_service.go` coverage (60.8% → 80%+)
3. Add handler unit tests
### Medium Priority
1. Add repository integration tests
2. Improve `organization_service.go` coverage (77.8% → 90%+)
3. Add E2E API tests
### Future Work
1. Frontend component tests (Vue Test Utils)
2. E2E tests (Playwright/Cypress)
3. Performance/load testing
## Security Tests
The protected-user system (v1.3.0) introduced a layered-defense testing pattern worth following for future security-critical features:
- **Per-layer unit tests:** Each layer is unit-tested in isolation (`pkg/middleware/protected_user_test.go`, `internal/service/admin_user_service_test.go`).
- **Integration tests for L3 (database):** Run on the per-DB matrix (`test/integration/protected_users_test.go`).
- **Recovery test matrix:** One test per failure mode, proving every recovery path actually recovers (`test/integration/protected_users_recovery_test.go`).
- **Adversarial bypass tests:** Each test deliberately bypasses outer layers and asserts the inner layer catches independently (`test/integration/protected_users_layered_defense_test.go`).
The adversarial pattern is particularly valuable: it proves that defense layers are not just present but *independently sufficient*. Use this pattern when adding new security-critical features.
---
*This file is maintained alongside test improvements. Update coverage stats after significant test additions.*