mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
627 lines
24 KiB
Python
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"])
|