""" Comprehensive tests for security/file_write_verifier.py Tests cover: - _sanitize_sensitive_data function - write_file_verified function - write_json_verified function - FileWriteSecurityError exception """ import json from pathlib import Path from unittest.mock import patch import pytest class TestSanitizeSensitiveData: """Tests for the _sanitize_sensitive_data function.""" def test_sanitizes_password_key(self): """Test that password keys are redacted.""" from local_deep_research.security.file_write_verifier import ( _sanitize_sensitive_data, ) data = {"username": "admin", "password": "secret123"} result = _sanitize_sensitive_data(data) assert result["username"] == "admin" assert result["password"] == "[REDACTED]" def test_sanitizes_api_key(self): """Test that api_key is redacted.""" from local_deep_research.security.file_write_verifier import ( _sanitize_sensitive_data, ) data = {"api_key": "sk-123456", "name": "test"} result = _sanitize_sensitive_data(data) assert result["api_key"] == "[REDACTED]" assert result["name"] == "test" def test_sanitizes_various_sensitive_keys(self): """Test that all sensitive key variations are redacted.""" from local_deep_research.security.file_write_verifier import ( _sanitize_sensitive_data, ) data = { "apikey": "value1", "api-key": "value2", "secret": "value3", "secret_key": "value4", "token": "value5", "access_token": "value6", "refresh_token": "value7", "private_key": "value8", "credentials": "value9", "auth": "value10", "authorization": "value11", } result = _sanitize_sensitive_data(data) for key in data: assert result[key] == "[REDACTED]" def test_case_insensitive_matching(self): """Test that matching is case insensitive.""" from local_deep_research.security.file_write_verifier import ( _sanitize_sensitive_data, ) data = {"PASSWORD": "secret", "Api_Key": "key123", "TOKEN": "tok"} result = _sanitize_sensitive_data(data) assert result["PASSWORD"] == "[REDACTED]" assert result["Api_Key"] == "[REDACTED]" assert result["TOKEN"] == "[REDACTED]" def test_sanitizes_nested_dicts(self): """Test that nested dictionaries are sanitized.""" from local_deep_research.security.file_write_verifier import ( _sanitize_sensitive_data, ) data = { "config": { "api_key": "secret", "settings": {"password": "hidden", "name": "test"}, } } result = _sanitize_sensitive_data(data) assert result["config"]["api_key"] == "[REDACTED]" assert result["config"]["settings"]["password"] == "[REDACTED]" assert result["config"]["settings"]["name"] == "test" def test_sanitizes_lists_of_dicts(self): """Test that lists containing dicts are sanitized.""" from local_deep_research.security.file_write_verifier import ( _sanitize_sensitive_data, ) data = { "users": [ {"name": "user1", "password": "pass1"}, {"name": "user2", "password": "pass2"}, ] } result = _sanitize_sensitive_data(data) assert result["users"][0]["password"] == "[REDACTED]" assert result["users"][1]["password"] == "[REDACTED]" assert result["users"][0]["name"] == "user1" def test_preserves_non_sensitive_data(self): """Test that non-sensitive data is preserved.""" from local_deep_research.security.file_write_verifier import ( _sanitize_sensitive_data, ) data = {"name": "test", "value": 123, "flag": True, "items": [1, 2, 3]} result = _sanitize_sensitive_data(data) assert result == data def test_handles_primitive_values(self): """Test that primitive values pass through unchanged.""" from local_deep_research.security.file_write_verifier import ( _sanitize_sensitive_data, ) assert _sanitize_sensitive_data("string") == "string" assert _sanitize_sensitive_data(123) == 123 assert _sanitize_sensitive_data(True) is True assert _sanitize_sensitive_data(None) is None def test_handles_list_of_primitives(self): """Test that lists of primitives pass through.""" from local_deep_research.security.file_write_verifier import ( _sanitize_sensitive_data, ) data = [1, 2, "test", True] result = _sanitize_sensitive_data(data) assert result == data def test_handles_non_string_keys(self): """Test handling of non-string dictionary keys.""" from local_deep_research.security.file_write_verifier import ( _sanitize_sensitive_data, ) data = {1: "value1", "password": "secret"} result = _sanitize_sensitive_data(data) assert result[1] == "value1" assert result["password"] == "[REDACTED]" class TestFileWriteSecurityError: """Tests for the FileWriteSecurityError exception.""" def test_exception_can_be_raised(self): """Test that the exception can be raised and caught.""" from local_deep_research.security.file_write_verifier import ( FileWriteSecurityError, ) with pytest.raises(FileWriteSecurityError) as exc_info: raise FileWriteSecurityError("Test error message") assert "Test error message" in str(exc_info.value) def test_exception_inherits_from_exception(self): """Test that FileWriteSecurityError inherits from Exception.""" from local_deep_research.security.file_write_verifier import ( FileWriteSecurityError, ) assert issubclass(FileWriteSecurityError, Exception) class TestWriteFileVerified: """Tests for the write_file_verified function.""" @pytest.fixture def mock_get_setting(self): """Fixture to mock the get_setting_from_snapshot function.""" with patch( "local_deep_research.config.search_config.get_setting_from_snapshot" ) as mock: yield mock def test_writes_file_when_setting_matches(self, tmp_path, mock_get_setting): """Test that file is written when setting matches required value.""" from local_deep_research.security.file_write_verifier import ( write_file_verified, ) filepath = tmp_path / "test.txt" content = "test content" mock_get_setting.return_value = True write_file_verified( filepath, content, "test.setting", required_value=True ) assert filepath.exists() assert filepath.read_text() == content def test_raises_error_when_setting_mismatch( self, tmp_path, mock_get_setting ): """Test that error is raised when setting doesn't match.""" from local_deep_research.security.file_write_verifier import ( write_file_verified, FileWriteSecurityError, ) filepath = tmp_path / "test.txt" mock_get_setting.return_value = False with pytest.raises(FileWriteSecurityError) as exc_info: write_file_verified( filepath, "content", "test.setting", required_value=True, context="test operation", ) assert "test operation" in str(exc_info.value) assert "test.setting=True" in str(exc_info.value) def test_raises_error_when_setting_not_found( self, tmp_path, mock_get_setting ): """Test that error is raised when setting doesn't exist.""" from local_deep_research.security.file_write_verifier import ( write_file_verified, FileWriteSecurityError, ) filepath = tmp_path / "test.txt" mock_get_setting.side_effect = KeyError("Setting not found") with pytest.raises(FileWriteSecurityError): write_file_verified(filepath, "content", "nonexistent.setting") def test_writes_binary_file(self, tmp_path, mock_get_setting): """Test writing binary content.""" from local_deep_research.security.file_write_verifier import ( write_file_verified, ) filepath = tmp_path / "test.bin" content = b"\x00\x01\x02\x03" mock_get_setting.return_value = True write_file_verified(filepath, content, "test.setting", mode="wb") assert filepath.exists() assert filepath.read_bytes() == content def test_uses_custom_encoding(self, tmp_path, mock_get_setting): """Test writing with custom encoding.""" from local_deep_research.security.file_write_verifier import ( write_file_verified, ) filepath = tmp_path / "test.txt" content = "日本語テスト" mock_get_setting.return_value = True write_file_verified(filepath, content, "test.setting", encoding="utf-8") assert filepath.exists() assert filepath.read_text(encoding="utf-8") == content def test_passes_settings_snapshot(self, tmp_path, mock_get_setting): """Test that settings_snapshot is passed to get_setting_from_snapshot.""" from local_deep_research.security.file_write_verifier import ( write_file_verified, ) filepath = tmp_path / "test.txt" snapshot = {"test.setting": True} mock_get_setting.return_value = True write_file_verified( filepath, "content", "test.setting", settings_snapshot=snapshot ) mock_get_setting.assert_called_once_with( "test.setting", settings_snapshot=snapshot ) def test_accepts_path_object(self, tmp_path, mock_get_setting): """Test that Path objects are accepted.""" from local_deep_research.security.file_write_verifier import ( write_file_verified, ) filepath = Path(tmp_path) / "test.txt" mock_get_setting.return_value = True write_file_verified(filepath, "content", "test.setting") assert filepath.exists() class TestWriteJsonVerified: """Tests for the write_json_verified function.""" @pytest.fixture def mock_get_setting(self): """Fixture to mock the get_setting_from_snapshot function.""" with patch( "local_deep_research.config.search_config.get_setting_from_snapshot" ) as mock: yield mock def test_writes_json_when_setting_matches(self, tmp_path, mock_get_setting): """Test that JSON is written when setting matches.""" from local_deep_research.security.file_write_verifier import ( write_json_verified, ) filepath = tmp_path / "test.json" data = {"key": "value", "number": 42} mock_get_setting.return_value = True write_json_verified(filepath, data, "test.setting") assert filepath.exists() written_data = json.loads(filepath.read_text()) assert written_data == data def test_sanitizes_sensitive_data(self, tmp_path, mock_get_setting): """Test that sensitive data is sanitized before writing.""" from local_deep_research.security.file_write_verifier import ( write_json_verified, ) filepath = tmp_path / "test.json" data = {"name": "test", "password": "secret123", "api_key": "key456"} mock_get_setting.return_value = True write_json_verified(filepath, data, "test.setting") written_data = json.loads(filepath.read_text()) assert written_data["name"] == "test" assert written_data["password"] == "[REDACTED]" assert written_data["api_key"] == "[REDACTED]" def test_uses_default_indent(self, tmp_path, mock_get_setting): """Test that default indent of 2 is used.""" from local_deep_research.security.file_write_verifier import ( write_json_verified, ) filepath = tmp_path / "test.json" data = {"key": "value"} mock_get_setting.return_value = True write_json_verified(filepath, data, "test.setting") content = filepath.read_text() # With indent=2, there should be newlines and spaces assert "\n" in content assert " " in content def test_custom_json_kwargs(self, tmp_path, mock_get_setting): """Test that custom JSON kwargs are passed through.""" from local_deep_research.security.file_write_verifier import ( write_json_verified, ) filepath = tmp_path / "test.json" data = {"b": 2, "a": 1} mock_get_setting.return_value = True write_json_verified( filepath, data, "test.setting", sort_keys=True, indent=4 ) content = filepath.read_text() # With sort_keys, 'a' should come before 'b' assert content.index('"a"') < content.index('"b"') # With indent=4, should have 4 spaces assert " " in content def test_writes_list_data(self, tmp_path, mock_get_setting): """Test writing list data.""" from local_deep_research.security.file_write_verifier import ( write_json_verified, ) filepath = tmp_path / "test.json" data = [{"name": "item1"}, {"name": "item2", "password": "secret"}] mock_get_setting.return_value = True write_json_verified(filepath, data, "test.setting") written_data = json.loads(filepath.read_text()) assert len(written_data) == 2 assert written_data[1]["password"] == "[REDACTED]" def test_raises_error_when_setting_mismatch( self, tmp_path, mock_get_setting ): """Test that error is raised when setting doesn't match.""" from local_deep_research.security.file_write_verifier import ( write_json_verified, FileWriteSecurityError, ) filepath = tmp_path / "test.json" mock_get_setting.return_value = False with pytest.raises(FileWriteSecurityError): write_json_verified(filepath, {"key": "value"}, "test.setting") class TestSensitiveKeys: """Tests for SENSITIVE_KEYS constant.""" def test_sensitive_keys_is_frozenset(self): """Test that SENSITIVE_KEYS is a frozenset.""" from local_deep_research.security.file_write_verifier import ( SENSITIVE_KEYS, ) assert isinstance(SENSITIVE_KEYS, frozenset) def test_sensitive_keys_contains_expected_values(self): """Test that SENSITIVE_KEYS contains expected sensitive key names.""" from local_deep_research.security.file_write_verifier import ( SENSITIVE_KEYS, ) expected_keys = { "password", "api_key", "secret", "token", "credentials", } for key in expected_keys: assert key in SENSITIVE_KEYS