Research-Stack/4-Infrastructure/NoDupeLabs/tests/integration/test_system_error_recovery.py

627 lines
24 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2025 Allaun
"""System Error Recovery Tests - Critical failure simulations and recovery.
This module tests system response to critical failures, backup and recovery
procedures, data integrity during error conditions, and system logging.
"""
import pytest
import sys
import os
import tempfile
import json
import time
from unittest.mock import patch, MagicMock
from nodupe.core.main import main
from nodupe.tools.commands.scan import ScanTool
from nodupe.tools.commands.apply import ApplyTool
from nodupe.tools.commands.similarity import SimilarityCommandTool as SimilarityTool
class TestCriticalFailureSimulations:
"""Test system response to critical failure scenarios."""
def test_scan_failure_with_invalid_paths(self):
"""Test scan failure handling with invalid paths."""
scan_tool = ScanTool()
scan_args = MagicMock()
scan_args.paths = ["/nonexistent/path", "/invalid/directory"]
scan_args.min_size = 0
scan_args.max_size = None
scan_args.extensions = None
scan_args.exclude = None
scan_args.verbose = False
scan_args.container = None
scan_result = scan_tool.execute_scan(scan_args)
assert scan_result != 0 # Should fail gracefully
def test_apply_failure_with_invalid_input(self):
"""Test apply failure handling with invalid input."""
apply_tool = ApplyTool()
apply_args = MagicMock()
apply_args.action = "list"
apply_args.input = "/nonexistent/file.json"
apply_args.target_dir = None
apply_args.dry_run = True
apply_args.verbose = False
apply_result = apply_tool.execute_apply(apply_args)
assert apply_result != 0 # Should fail gracefully
def test_similarity_failure_with_invalid_query(self):
"""Test similarity failure handling with invalid query."""
similarity_tool = SimilarityTool()
similarity_args = MagicMock()
similarity_args.query_file = "/nonexistent/query.txt"
similarity_args.database = None
similarity_args.k = 5
similarity_args.threshold = 0.7
similarity_args.backend = "brute_force"
similarity_args.output = "text"
similarity_args.verbose = False
similarity_result = similarity_tool.execute_similarity(similarity_args)
assert similarity_result != 0 # Should fail gracefully
def test_scan_failure_with_permission_denied(self):
"""Test scan failure handling with permission denied."""
scan_tool = ScanTool()
scan_args = MagicMock()
scan_args.paths = ["/root", "/etc"] # Protected directories
scan_args.min_size = 0
scan_args.max_size = None
scan_args.extensions = None
scan_args.exclude = None
scan_args.verbose = False
scan_args.container = None
scan_result = scan_tool.execute_scan(scan_args)
# Should handle permission errors gracefully
assert isinstance(scan_result, int)
class TestDataCorruptionRecovery:
"""Test recovery from data corruption scenarios."""
def test_apply_recovery_with_corrupted_duplicates_file(self):
"""Test apply recovery with corrupted duplicates file."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create corrupted duplicates file
corrupted_file = os.path.join(temp_dir, "corrupted.json")
with open(corrupted_file, "w") as f:
f.write("{ invalid json content }") # Corrupted JSON
apply_tool = ApplyTool()
apply_args = MagicMock()
apply_args.action = "list"
apply_args.input = corrupted_file
apply_args.target_dir = None
apply_args.dry_run = True
apply_args.verbose = False
apply_result = apply_tool.execute_apply(apply_args)
# Should handle corrupted file gracefully
assert isinstance(apply_result, int)
def test_scan_recovery_with_corrupted_files(self):
"""Test scan recovery with corrupted file content."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create files with some potentially corrupted content
for i in range(10):
test_file = os.path.join(temp_dir, f"file_{i}.txt")
if i % 3 == 0:
# Create file with binary-like content
with open(test_file, "wb") as f:
f.write(b'\x00\x01\x02\x03\x04\x05') # Binary content
else:
with open(test_file, "w") as f:
f.write(f"Normal content {i}")
# Mock container and services
mock_container = MagicMock()
mock_db_connection = MagicMock()
mock_container.get_service.return_value = mock_db_connection
scan_tool = ScanTool()
scan_args = MagicMock()
scan_args.paths = [temp_dir]
scan_args.min_size = 0
scan_args.max_size = None
scan_args.extensions = None
scan_args.exclude = None
scan_args.verbose = False
scan_args.container = mock_container
scan_result = scan_tool.execute_scan(scan_args)
# Should handle mixed file content gracefully
assert isinstance(scan_result, int)
class TestBackupRecoveryProcedures:
"""Test backup and recovery procedures."""
def test_scan_recovery_with_partial_success(self):
"""Test scan recovery with partial success scenarios."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create mix of valid and invalid paths
valid_dir = os.path.join(temp_dir, "valid")
invalid_dir = os.path.join(temp_dir, "invalid_nonexistent")
os.makedirs(valid_dir)
# Create valid files
for i in range(5):
test_file = os.path.join(valid_dir, f"valid_{i}.txt")
with open(test_file, "w") as f:
f.write(f"Valid content {i}")
# Mock container and services
mock_container = MagicMock()
mock_db_connection = MagicMock()
mock_container.get_service.return_value = mock_db_connection
scan_tool = ScanTool()
scan_args = MagicMock()
scan_args.paths = [valid_dir, invalid_dir] # Mix of valid and invalid
scan_args.min_size = 0
scan_args.max_size = None
scan_args.extensions = None
scan_args.exclude = None
scan_args.verbose = False
scan_args.container = mock_container
scan_result = scan_tool.execute_scan(scan_args)
# Should handle partial success gracefully
assert isinstance(scan_result, int)
def test_apply_recovery_with_partial_data(self):
"""Test apply recovery with partial data scenarios."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create duplicates file with partial data
partial_file = os.path.join(temp_dir, "partial.json")
partial_data = {
"duplicate_groups": [
{
"hash": "partial_hash_1",
"files": [
{"path": "/tmp/existing_file.txt", "size": 100, "type": "txt"},
{"path": "/tmp/nonexistent_file.txt", "size": 100, "type": "txt"}
]
},
{
"hash": "partial_hash_2",
"files": [] # Empty files list
}
]
}
with open(partial_file, "w") as f:
json.dump(partial_data, f)
apply_tool = ApplyTool()
apply_args = MagicMock()
apply_args.action = "list"
apply_args.input = partial_file
apply_args.target_dir = None
apply_args.dry_run = True
apply_args.verbose = False
apply_result = apply_tool.execute_apply(apply_args)
# Should handle partial data gracefully
assert isinstance(apply_result, int)
class TestSystemLogging:
"""Test system logging and monitoring capabilities."""
def test_scan_logging_with_verbose_output(self):
"""Test scan logging with verbose output enabled."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create test files
for i in range(5):
test_file = os.path.join(temp_dir, f"log_test_{i}.txt")
with open(test_file, "w") as f:
f.write(f"Logging test {i}")
# Mock container and services
mock_container = MagicMock()
mock_db_connection = MagicMock()
mock_container.get_service.return_value = mock_db_connection
scan_tool = ScanTool()
scan_args = MagicMock()
scan_args.paths = [temp_dir]
scan_args.min_size = 0
scan_args.max_size = None
scan_args.extensions = None
scan_args.exclude = None
scan_args.verbose = True # Enable verbose logging
scan_args.container = mock_container
scan_result = scan_tool.execute_scan(scan_args)
assert scan_result == 0 # Should complete with verbose logging
def test_apply_logging_with_verbose_output(self):
"""Test apply logging with verbose output enabled."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create duplicates file
duplicates_file = os.path.join(temp_dir, "logging_duplicates.json")
logging_data = {
"duplicate_groups": [
{
"hash": "logging_hash",
"files": [
{"path": "/tmp/log_file1.txt", "size": 150, "type": "txt"},
{"path": "/tmp/log_file2.txt", "size": 150, "type": "txt"}
]
}
]
}
with open(duplicates_file, "w") as f:
json.dump(logging_data, f)
apply_tool = ApplyTool()
apply_args = MagicMock()
apply_args.action = "list"
apply_args.input = duplicates_file
apply_args.target_dir = None
apply_args.dry_run = True
apply_args.verbose = True # Enable verbose logging
apply_result = apply_tool.execute_apply(apply_args)
assert apply_result == 0 # Should complete with verbose logging
class TestErrorInjection:
"""Test error injection and recovery scenarios."""
def test_scan_error_injection_with_invalid_container(self):
"""Test scan error injection with invalid container."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create test files
for i in range(3):
test_file = os.path.join(temp_dir, f"error_{i}.txt")
with open(test_file, "w") as f:
f.write(f"Error injection test {i}")
# Test with invalid container
scan_tool = ScanTool()
scan_args = MagicMock()
scan_args.paths = [temp_dir]
scan_args.min_size = 0
scan_args.max_size = None
scan_args.extensions = None
scan_args.exclude = None
scan_args.verbose = False
scan_args.container = "invalid_container" # Invalid container
scan_result = scan_tool.execute_scan(scan_args)
# Should handle invalid container gracefully
assert isinstance(scan_result, int)
def test_apply_error_injection_with_invalid_action(self):
"""Test apply error injection with invalid action."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create duplicates file
duplicates_file = os.path.join(temp_dir, "error_duplicates.json")
error_data = {
"duplicate_groups": [
{
"hash": "error_hash",
"files": [
{"path": "/tmp/error_file.txt", "size": 200, "type": "txt"}
]
}
]
}
with open(duplicates_file, "w") as f:
json.dump(error_data, f)
apply_tool = ApplyTool()
apply_args = MagicMock()
apply_args.action = "invalid_action" # Invalid action
apply_args.input = duplicates_file
apply_args.target_dir = None
apply_args.dry_run = True
apply_args.verbose = False
apply_result = apply_tool.execute_apply(apply_args)
# Should handle invalid action gracefully
assert isinstance(apply_result, int)
class TestCriticalFailureRecovery:
"""Test recovery from critical system failures."""
def test_scan_recovery_from_critical_failure(self):
"""Test scan recovery from critical failure scenarios."""
# Test with completely invalid configuration
scan_tool = ScanTool()
scan_args = MagicMock()
scan_args.paths = None # No paths provided
scan_args.min_size = -1 # Invalid size
scan_args.max_size = -1 # Invalid size
scan_args.extensions = None
scan_args.exclude = None
scan_args.verbose = False
scan_args.container = None
scan_result = scan_tool.execute_scan(scan_args)
# Should handle critical failure gracefully
assert isinstance(scan_result, int)
def test_apply_recovery_from_critical_failure(self):
"""Test apply recovery from critical failure scenarios."""
# Test with completely invalid configuration
apply_tool = ApplyTool()
apply_args = MagicMock()
apply_args.action = None # No action provided
apply_args.input = None # No input provided
apply_args.target_dir = None
apply_args.dry_run = True
apply_args.verbose = False
apply_result = apply_tool.execute_apply(apply_args)
# Should handle critical failure gracefully
assert isinstance(apply_result, int)
class TestDataIntegrity:
"""Test data integrity during error conditions."""
def test_scan_data_integrity_with_error_conditions(self):
"""Test scan data integrity with various error conditions."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create test files with mixed content
for i in range(10):
test_file = os.path.join(temp_dir, f"integrity_{i}.txt")
if i % 4 == 0:
# Create empty file
open(test_file, 'w').close()
elif i % 4 == 1:
# Create file with special characters
with open(test_file, "w", encoding='utf-8') as f:
f.write("Special chars: ñ, ü, é, ß, ©, ®")
else:
with open(test_file, "w") as f:
f.write(f"Normal integrity test {i}")
# Mock container and services
mock_container = MagicMock()
mock_db_connection = MagicMock()
mock_container.get_service.return_value = mock_db_connection
scan_tool = ScanTool()
scan_args = MagicMock()
scan_args.paths = [temp_dir]
scan_args.min_size = 0
scan_args.max_size = None
scan_args.extensions = None
scan_args.exclude = None
scan_args.verbose = False
scan_args.container = mock_container
scan_result = scan_tool.execute_scan(scan_args)
# Should maintain data integrity
assert isinstance(scan_result, int)
def test_apply_data_integrity_with_error_conditions(self):
"""Test apply data integrity with various error conditions."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create duplicates file with mixed data quality
mixed_file = os.path.join(temp_dir, "mixed_integrity.json")
mixed_data = {
"duplicate_groups": [
{
"hash": "integrity_hash_1",
"files": [
{"path": "/tmp/normal_file.txt", "size": 100, "type": "txt"},
{"path": "/tmp/normal_file2.txt", "size": 100, "type": "txt"}
]
},
{
"hash": "integrity_hash_2",
"files": [
{"path": "", "size": 0, "type": ""} # Empty/missing data
]
},
{
"hash": "integrity_hash_3",
"files": [
{"path": "/tmp/special_chars_ñ.txt", "size": 150, "type": "txt"}
]
}
]
}
with open(mixed_file, "w", encoding='utf-8') as f:
json.dump(mixed_data, f)
apply_tool = ApplyTool()
apply_args = MagicMock()
apply_args.action = "list"
apply_args.input = mixed_file
apply_args.target_dir = None
apply_args.dry_run = True
apply_args.verbose = False
apply_result = apply_tool.execute_apply(apply_args)
# Should maintain data integrity
assert isinstance(apply_result, int)
class TestSystemMonitoring:
"""Test system monitoring and error reporting."""
def test_scan_monitoring_with_error_reporting(self):
"""Test scan monitoring with comprehensive error reporting."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create test files
for i in range(5):
test_file = os.path.join(temp_dir, f"monitor_{i}.txt")
with open(test_file, "w") as f:
f.write(f"Monitoring test {i}")
# Mock container and services with monitoring
mock_container = MagicMock()
mock_db_connection = MagicMock()
mock_container.get_service.return_value = mock_db_connection
scan_tool = ScanTool()
scan_args = MagicMock()
scan_args.paths = [temp_dir]
scan_args.min_size = 0
scan_args.max_size = None
scan_args.extensions = None
scan_args.exclude = None
scan_args.verbose = True # Enable monitoring
scan_args.container = mock_container
scan_result = scan_tool.execute_scan(scan_args)
assert scan_result == 0 # Should complete with monitoring
def test_apply_monitoring_with_error_reporting(self):
"""Test apply monitoring with comprehensive error reporting."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create duplicates file
duplicates_file = os.path.join(temp_dir, "monitoring_duplicates.json")
monitoring_data = {
"duplicate_groups": [
{
"hash": "monitoring_hash",
"files": [
{"path": "/tmp/monitor_file1.txt", "size": 200, "type": "txt"},
{"path": "/tmp/monitor_file2.txt", "size": 200, "type": "txt"}
]
}
]
}
with open(duplicates_file, "w") as f:
json.dump(monitoring_data, f)
apply_tool = ApplyTool()
apply_args = MagicMock()
apply_args.action = "list"
apply_args.input = duplicates_file
apply_args.target_dir = None
apply_args.dry_run = True
apply_args.verbose = True # Enable monitoring
apply_result = apply_tool.execute_apply(apply_args)
assert apply_result == 0 # Should complete with monitoring
class TestRecoveryValidation:
"""Test validation of recovery procedures."""
def test_scan_recovery_validation(self):
"""Test validation of scan recovery procedures."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create test files
for i in range(8):
test_file = os.path.join(temp_dir, f"recovery_{i}.txt")
with open(test_file, "w") as f:
f.write(f"Recovery validation {i}")
# Test multiple recovery scenarios
scan_tool = ScanTool()
test_scenarios = [
# Normal scenario
{
"paths": [temp_dir],
"container": MagicMock(),
"expected": 0
},
# Missing container scenario
{
"paths": [temp_dir],
"container": None,
"expected": "int"
},
# Invalid path scenario
{
"paths": ["/invalid/path"],
"container": None,
"expected": "int"
}
]
for scenario in test_scenarios:
mock_container = scenario["container"]
if mock_container:
mock_db_connection = MagicMock()
mock_container.get_service.return_value = mock_db_connection
scan_args = MagicMock()
scan_args.paths = scenario["paths"]
scan_args.min_size = 0
scan_args.max_size = None
scan_args.extensions = None
scan_args.exclude = None
scan_args.verbose = False
scan_args.container = scenario["container"]
scan_result = scan_tool.execute_scan(scan_args)
if scenario["expected"] == 0:
assert scan_result == 0
else:
assert isinstance(scan_result, int)
def test_apply_recovery_validation(self):
"""Test validation of apply recovery procedures."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create valid duplicates file
valid_file = os.path.join(temp_dir, "valid_duplicates.json")
valid_data = {
"duplicate_groups": [
{
"hash": "valid_hash",
"files": [
{"path": "/tmp/valid1.txt", "size": 100, "type": "txt"},
{"path": "/tmp/valid2.txt", "size": 100, "type": "txt"}
]
}
]
}
with open(valid_file, "w") as f:
json.dump(valid_data, f)
# Test multiple recovery scenarios
apply_tool = ApplyTool()
test_scenarios = [
# Normal scenario
{
"input": valid_file,
"action": "list",
"expected": 0
},
# Invalid file scenario
{
"input": "/invalid/file.json",
"action": "list",
"expected": "int"
},
# Invalid action scenario
{
"input": valid_file,
"action": "invalid",
"expected": "int"
}
]
for scenario in test_scenarios:
apply_args = MagicMock()
apply_args.action = scenario["action"]
apply_args.input = scenario["input"]
apply_args.target_dir = None
apply_args.dry_run = True
apply_args.verbose = False
apply_result = apply_tool.execute_apply(apply_args)
if scenario["expected"] == 0:
assert apply_result == 0
else:
assert isinstance(apply_result, int)
if __name__ == "__main__":
pytest.main([__file__, "-v"])