Ctrl K
ComunidadGuías…

docs/TESTING.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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.*