Перейти к содержанию

ADR-0008: Isolated Database Testing Architecture

Статус

Accepted

Контекст

При росте количества интеграционных тестов возникли проблемы с legacy cleanup подходом: - Тесты влияют друг на друга через shared database state - Race conditions между параллельными тестами - Сложная логика cleanup с риском удаления не-тестовых данных - Высокая стоимость поддержки cleanup функций - Медленная очистка БД между тестами

Для надежного тестирования финансовой системы критически важна полная изоляция тестовых данных.

Решение

Внедрение PostgreSQL TEMPLATE системы для полной изоляции каждого теста в отдельной базе данных.

Isolated Database Architecture:

  1. PostgreSQL TEMPLATE DATABASE - базовая пустая БД со схемой
  2. Изолированные тестовые БД - каждый тест получает свою копию
  3. Автоматическая очистка - DROP DATABASE при завершении теста
  4. Zero cleanup logic - никаких ручных cleanup функций

Техническая архитектура:

saga_empty_template (master template)
    ↓ CREATE DATABASE test_saga_123 TEMPLATE saga_empty_template
test_saga_123 (isolated test DB)
    ↓ Тест завершен
DROP DATABASE test_saga_123 (automatic cleanup)

Ключевые компоненты:

backend/tests/isolated_db_helper.go (1133+ строк)
├── CreateIsolatedTestDB(t)      # Основная функция для тестов
├── IsolatedTestDB struct        # 50+ helper методов
├── Automatic cleanup logic      # t.Cleanup() integration
└── Template management          # Setup saga_empty_template

Архитектурные принципы:

  • Каждый тест = отдельная база данных
  • Полная изоляция данных между тестами
  • Automatic cleanup без ручного кода
  • PostgreSQL TEMPLATE обеспечивает скорость создания

Альтернативы

Legacy Cleanup Functions

Заменена - сложная логика, race conditions, риск удаления production данных, медленная работа.

Transaction Rollback Pattern

Отклонена - не работает с commit/rollback логикой в приложении, сложность nested transactions.

Docker Test Containers

Отклонена - медленный startup контейнеров, избыточная сложность для локальной разработки.

In-Memory Database (SQLite)

Отклонена - не соответствует PostgreSQL особенностям (JSON, индексы, constraint).

Последствия

Положительные

  • Полная изоляция - тесты не влияют друг на друга
  • Параллельное выполнение - без race conditions
  • Безопасность - невозможно удалить production данные
  • Простота - нет сложной cleanup логики
  • Скорость - CREATE DATABASE быстрее чем DELETE операции
  • Надежность - guaranteed cleanup через PostgreSQL DROP

Отрицательные

  • Немного больше overhead на создание БД
  • Требует правильной настройки PostgreSQL template
  • Необходимость миграции существующих тестов

Нейтральные

  • Изменение привычного подхода к написанию тестов
  • Необходимость обучения команды новому подходу

Implementation Details

Использование в тестах:

func TestUserOperations(t *testing.T) {
    // Создание изолированной БД (автоматически сгенерированное имя)
    isolatedDB := tests.CreateIsolatedTestDB(t)
    // Автоматическая очистка через t.Cleanup()

    // Создание тестового пользователя
    userID := isolatedDB.CreateTestUser(t, "test@saga-test.com")

    // Создание депозита
    amount, _ := models.NewSafeDecimalFromString("100.00")
    deposit, err := isolatedDB.CreateTestDeposit(ctx, userID, amount)
    require.NoError(t, err)

    // Проверка баланса
    balance := isolatedDB.GetUserBalance(t, userID, "USDC")
    assert.Equal(t, amount, balance)

    // НИКАКИХ cleanup операций не требуется!
    // PostgreSQL автоматически удалит БД через t.Cleanup()
}

Helper методы IsolatedTestDB:

// User management
func (db *IsolatedTestDB) CreateTestUser(t, email) string
func (db *IsolatedTestDB) CountUsers(t) int
func (db *IsolatedTestDB) GetUserByEmail(t, email) *models.User

// Financial operations
func (db *IsolatedTestDB) CreateTestDeposit(ctx, userID, amount) (*models.Transaction, error)
func (db *IsolatedTestDB) CreateTestWithdrawal(ctx, userID, amount) (*models.Transaction, error)
func (db *IsolatedTestDB) GetUserBalance(t, userID, currency) SafeDecimal

// Wallet management
func (db *IsolatedTestDB) CreateTestWallet(t, userID, address) string
func (db *IsolatedTestDB) GetUserWallets(t, userID) []*models.Wallet

// Investment operations
func (db *IsolatedTestDB) CreateTestInvestment(ctx, userID, strategy, amount) (*models.Investment, error)
func (db *IsolatedTestDB) CountInvestments(t) int

// Automatic cleanup through t.Cleanup() - no manual cleanup needed!

Template Database Setup:

-- Template БД создается один раз при первом использовании
CREATE DATABASE saga_empty_template;
-- Применяются все миграции для полной схемы
-- Template остается пустой но со всей структурой

Migration Strategy

Текущий статус (Сентябрь 2025):

  • Архитектура реализована - isolated_db_helper.go (1133+ строк)
  • 100+ тестов мигрированы - используют CreateIsolatedTestDB()
  • ⚠️ Legacy тесты остались - 8+ интеграционных тестов все еще используют cleanup функции
  • ⚠️ Legacy файлы сохранены - до завершения миграции всех тестов

План завершения:

  1. Мигрировать оставшиеся интеграционные тесты на isolated подход
  2. Удалить legacy cleanup файлы: cleanup_test_user_fixed.go, immutable_transaction_cleanup.go, test_cleanup.go
  3. Обновить CI/CD пайплайн для использования только isolated тестов

Файлы для миграции:

  • backend/tests/integration/no_gas_system_test.go
  • backend/tests/integration/cleanup_safety_test.go
  • backend/tests/integration/investment_details_api_integration_test.go
  • backend/tests/integration/auth_comprehensive_test.go
  • backend/tests/integration/real_user_deposits_verification_test.go
  • backend/shared/services/balance_service_test.go
  • backend/integration_tests/database_interface_test.go

Связанные решения

  • Связано с: ADR-0003 (No-Mock Testing Architecture) - для реального database testing
  • Связано с: ADR-0007 (Database Schema Design) - для template БД структуры
  • Связано с: ADR-0006 (Centralized Test Config) - для тестовых данных

Критические принципы

Принципы изолированного тестирования:

  1. Один тест = одна БД - каждый CreateIsolatedTestDB() создает уникальную БД
  2. Zero cleanup code - никаких ручных cleanup функций в тестах
  3. Automatic lifecycle - создание через TEMPLATE, удаление через t.Cleanup()
  4. Full isolation - тесты НИКОГДА не влияют друг на друга
  5. PostgreSQL native - используем TEMPLATE возможности БД

Критическое правило: НИКОГДА не использовать legacy cleanup функции в новых тестах. Если тест создает данные в БД - использовать CreateIsolatedTestDB().

Performance characteristics:

  • CREATE DATABASE from TEMPLATE: ~50-100ms
  • Legacy cleanup DELETE operations: ~200-500ms
  • Isolated approach: 2-5x faster + guaranteed isolation

Безопасность:

  • Невозможно случайно удалить production данные
  • Каждый тест работает в изолированной среде
  • Automatic cleanup исключает забытые тестовые данные
  • PostgreSQL ACID guarantees для всех операций

Примечания

Настройка PostgreSQL для isolated testing:

-- Убедиться что template1 доступна для создания БД
GRANT CREATE ON DATABASE template1 TO saga_user;
-- Или настроить отдельную template БД для тестов

Мониторинг использования:

  • Template database должна оставаться пустой
  • Isolated test databases создаются и удаляются автоматически
  • Логирование создания/удаления изолированных БД для debugging

Best Practices:

  • Всегда используй CreateIsolatedTestDB(t) для новых тестов
  • НЕ создавай manual cleanup logic
  • НЕ используй shared test database state
  • Используй helper methods для common operations
  • Testdata generation через isolated DB helpers

Архитектурное преимущество: Isolated Database Architecture устраняет класс проблем связанных с shared mutable state в тестах, обеспечивая 100% надежность и reproducibility тестирования финансовой системы.