baktainer/app/spec/unit/label_validator_spec.rb

164 lines
4.9 KiB
Ruby
Raw Normal View History

Major architectural overhaul: dependency injection, monitoring, and operational improvements This commit represents a comprehensive refactoring and enhancement of Baktainer: ## Core Architecture Improvements - Implemented comprehensive dependency injection system with DependencyContainer - Fixed critical singleton instantiation bug that was returning Procs instead of service instances - Replaced problematic Concurrent::FixedThreadPool with custom SimpleThreadPool implementation - Achieved 100% test pass rate (121 examples, 0 failures) after fixing 30+ failing tests ## New Features Implemented ### 1. Backup Rotation & Cleanup (BackupRotation) - Configurable retention policies by age, count, and disk space - Automatic cleanup with comprehensive statistics tracking - Empty directory cleanup and space monitoring ### 2. Backup Encryption (BackupEncryption) - AES-256-CBC and AES-256-GCM encryption support - Key derivation from passphrases or direct key input - Encrypted backup metadata storage ### 3. Operational Monitoring Suite - **Health Check Server**: HTTP endpoints for monitoring (/health, /status, /metrics) - **Web Dashboard**: Real-time monitoring dashboard with auto-refresh - **Prometheus Metrics**: Integration with monitoring systems - **Backup Monitor**: Comprehensive metrics tracking and performance alerts ### 4. Advanced Label Validation (LabelValidator) - Schema-based validation for all 12+ Docker labels - Engine-specific validation rules - Helpful error messages and warnings - Example generation for each database engine ### 5. Multi-Channel Notifications (NotificationSystem) - Support for Slack, Discord, Teams, webhooks, and log notifications - Event-based notifications for backups, failures, warnings, and health issues - Configurable notification thresholds ## Code Organization Improvements - Extracted responsibilities into focused classes: - ContainerValidator: Container validation logic - BackupOrchestrator: Backup workflow orchestration - FileSystemOperations: File I/O with comprehensive error handling - Configuration: Centralized environment variable management - BackupStrategy/Factory: Strategy pattern for database engines ## Testing Infrastructure - Added comprehensive unit and integration tests - Fixed timing-dependent test failures - Added RSpec coverage reporting (94.94% coverage) - Created test factories and fixtures ## Breaking Changes - Container class constructor now requires dependency injection - BackupCommand methods now use keyword arguments - Thread pool implementation changed from Concurrent to SimpleThreadPool ## Configuration New environment variables: - BT_HEALTH_SERVER_ENABLED: Enable health check server - BT_HEALTH_PORT/BT_HEALTH_BIND: Health server configuration - BT_NOTIFICATION_CHANNELS: Comma-separated notification channels - BT_ENCRYPTION_ENABLED/BT_ENCRYPTION_KEY: Backup encryption - BT_RETENTION_DAYS/COUNT: Backup retention policies This refactoring improves maintainability, testability, and adds enterprise-grade monitoring and operational features while maintaining backward compatibility for basic usage. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-14 22:58:26 -04:00
# frozen_string_literal: true
require 'spec_helper'
require 'baktainer/label_validator'
RSpec.describe Baktainer::LabelValidator do
let(:logger) { double('Logger', debug: nil, info: nil, warn: nil, error: nil) }
let(:validator) { described_class.new(logger) }
describe '#validate' do
context 'with valid MySQL labels' do
let(:labels) do
{
'baktainer.backup' => 'true',
'baktainer.db.engine' => 'mysql',
'baktainer.db.name' => 'myapp_production',
'baktainer.db.user' => 'backup_user',
'baktainer.db.password' => 'secure_password'
}
end
it 'returns valid result' do
result = validator.validate(labels)
expect(result[:valid]).to be true
expect(result[:errors]).to be_empty
end
it 'normalizes boolean values' do
result = validator.validate(labels)
expect(result[:normalized_labels]['baktainer.backup']).to be true
end
end
context 'with valid SQLite labels' do
let(:labels) do
{
'baktainer.backup' => 'true',
'baktainer.db.engine' => 'sqlite',
'baktainer.db.name' => 'app_db'
}
end
it 'returns valid result without auth requirements' do
result = validator.validate(labels)
if !result[:valid]
puts "Validation errors: #{result[:errors]}"
puts "Validation warnings: #{result[:warnings]}"
end
expect(result[:valid]).to be true
end
end
context 'with missing required labels' do
let(:labels) do
{
'baktainer.backup' => 'true'
# Missing engine and name
}
end
it 'returns invalid result with errors' do
result = validator.validate(labels)
expect(result[:valid]).to be false
expect(result[:errors]).to include(match(/baktainer.db.engine/))
expect(result[:errors]).to include(match(/baktainer.db.name/))
end
end
context 'with invalid engine' do
let(:labels) do
{
'baktainer.backup' => 'true',
'baktainer.db.engine' => 'invalid_engine',
'baktainer.db.name' => 'mydb'
}
end
it 'returns invalid result' do
result = validator.validate(labels)
expect(result[:valid]).to be false
expect(result[:errors]).to include(match(/Invalid value.*invalid_engine/))
end
end
context 'with invalid database name format' do
let(:labels) do
{
'baktainer.backup' => 'true',
'baktainer.db.engine' => 'mysql',
'baktainer.db.name' => 'invalid name with spaces!',
'baktainer.db.user' => 'user',
'baktainer.db.password' => 'pass'
}
end
it 'returns invalid result' do
result = validator.validate(labels)
expect(result[:valid]).to be false
expect(result[:errors]).to include(match(/format invalid/))
end
end
context 'with unknown labels' do
let(:labels) do
{
'baktainer.backup' => 'true',
'baktainer.db.engine' => 'mysql',
'baktainer.db.name' => 'mydb',
'baktainer.db.user' => 'user',
'baktainer.db.password' => 'pass',
'baktainer.unknown.label' => 'value'
}
end
it 'includes warnings for unknown labels' do
result = validator.validate(labels)
expect(result[:valid]).to be true
expect(result[:warnings]).to include(match(/Unknown baktainer label/))
end
end
end
describe '#get_label_help' do
it 'returns help for known label' do
help = validator.get_label_help('baktainer.db.engine')
expect(help).to include('Database engine type')
expect(help).to include('Required: Yes')
expect(help).to include('mysql, mariadb, postgres')
end
it 'returns nil for unknown label' do
help = validator.get_label_help('unknown.label')
expect(help).to be_nil
end
end
describe '#generate_example_labels' do
it 'generates valid MySQL example' do
labels = validator.generate_example_labels('mysql')
expect(labels['baktainer.db.engine']).to eq('mysql')
expect(labels['baktainer.db.user']).not_to be_nil
expect(labels['baktainer.db.password']).not_to be_nil
end
it 'generates valid SQLite example without auth' do
labels = validator.generate_example_labels('sqlite')
expect(labels['baktainer.db.engine']).to eq('sqlite')
expect(labels).not_to have_key('baktainer.db.user')
expect(labels).not_to have_key('baktainer.db.password')
end
end
describe '#validate_single_label' do
it 'validates individual label' do
result = validator.validate_single_label('baktainer.db.engine', 'mysql')
expect(result[:valid]).to be true
end
it 'detects invalid individual label' do
result = validator.validate_single_label('baktainer.db.engine', 'invalid')
expect(result[:valid]).to be false
end
end
end