Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
41 KiB
JSON
1 line
41 KiB
JSON
{"repo": "bitfaced2/qubes", "files": {"tests/unit/test_secure_settings.py": {"75c6d066f765635b071661e181e18d76e9e26da5": {"t": 1785746913.170939, "n": 13164, "h": "9aeca824e3ed322d", "c": "\"\"\"\nTests for SecureSettingsManager (API Key Management)\n\nTests encrypted storage and validation of API keys.\n\"\"\"\n\nimport pytest\nimport tempfile\nimport shutil\nfrom pathlib import Path\n\nfrom config import APIKeys, SecureSettingsManager\nfrom core.exceptions import EncryptionError, DecryptionError\n\n\nclass TestAPIKeys:\n \"\"\"Test APIKeys dataclass\"\"\"\n\n def test_default_api_keys(self):\n \"\"\"Test default API keys (all None)\"\"\"\n keys = APIKeys()\n\n assert keys.openai is None\n assert keys.anthropic is None\n assert keys.google is None\n assert keys.deepseek is None\n assert keys.perplexity is None\n assert keys.pinata_jwt is None\n assert keys.elevenlabs is None\n assert keys.deepgram is None\n\n def test_custom_api_keys(self):\n \"\"\"Test custom API keys\"\"\"\n keys = APIKeys(\n openai=\"sk-test-openai\",\n anthropic=\"sk-ant-test\",\n google=\"test-google-key\"\n )\n\n assert keys.openai == \"sk-test-openai\"\n assert keys.anthropic == \"sk-ant-test\"\n assert keys.google == \"test-google-key\"\n assert keys.deepseek is None # Not set\n\n def test_to_dict_excludes_none(self):\n \"\"\"Test to_dict() excludes None values\"\"\"\n keys = APIKeys(\n openai=\"sk-test\",\n anthropic=None,\n google=\"google-test\"\n )\n\n keys_dict = keys.to_dict()\n\n assert \"openai\" in keys_dict\n assert \"google\" in keys_dict\n assert \"anthropic\" not in keys_dict # Excluded because None\n\n def test_is_empty(self):\n \"\"\"Test is_empty() method\"\"\"\n # Empty keys\n empty_keys = APIKeys()\n assert empty_keys.is_empty() is True\n\n # Non-empty keys\n non_empty_keys = APIKeys(openai=\"sk-test\")\n assert non_empty_keys.is_empty() is False\n\n\nclass TestSecureSettingsManager:\n \"\"\"Test SecureSettingsManager class\"\"\"\n\n @pytest.fixture\n def temp_user_dir(self):\n \"\"\"Create temporary user directory\"\"\"\n temp_dir = Path(tempfile.mkdtemp())\n yield temp_dir\n # Cleanup\n if temp_dir.exists():\n shutil.rmtree(temp_dir)\n\n @pytest.fixture\n def secure_manager(self, temp_user_dir):\n \"\"\"Create test secure settings manager\"\"\"\n manager = SecureSettingsManager(temp_user_dir, master_password=\"test_password\")\n return manager\n\n def test_initialization(self, temp_user_dir):\n \"\"\"Test secure settings manager initialization\"\"\"\n manager = SecureSettingsManager(temp_user_dir, master_password=\"test_pass\")\n\n assert manager.user_data_dir == temp_user_dir\n assert manager.user_data_dir.exists()\n assert manager.api_keys_file == temp_user_dir / \"api_keys.enc\"\n assert manager.salt_file == temp_user_dir / \"salt.bin\"\n\n def test_set_master_password(self, temp_user_dir):\n \"\"\"Test setting master password\"\"\"\n manager = SecureSettingsManager(temp_user_dir)\n assert manager._master_password is None\n\n manager.set_master_password(\"my_password\")\n assert manager._master_password == \"my_password\"\n\n def test_save_and_load_api_keys(self, secure_manager):\n \"\"\"Test saving and loading encrypted API keys\"\"\"\n # Create test API keys\n api_keys = APIKeys(\n openai=\"sk-test-openai-key\",\n anthropic=\"sk-ant-test-key\",\n google=\"google-test-key\"\n )\n\n # Save\n secure_manager.save_api_keys(api_keys)\n\n # Verify encrypted file exists\n assert secure_manager.api_keys_file.exists()\n\n # Load\n loaded_keys = secure_manager.load_api_keys()\n\n # Verify\n assert loaded_keys.openai == \"sk-test-openai-key\"\n assert loaded_keys.anthropic == \"sk-ant-test-key\"\n assert loaded_keys.google == \"google-test-key\"\n assert loaded_keys.deepseek is None\n\n def test_load_nonexistent_keys_returns_empty(self, secure_manager):\n \"\"\"Test loading API keys when file doesn't exist\"\"\"\n # File shouldn't exist yet\n assert not secure_manager.api_keys_file.exists()\n\n # Should return empty APIKeys\n loaded_keys = secure_manager.load_api_keys()\n\n assert loaded_keys.is_empty() is True\n\n def test_encryption_requires_master_password(self, temp_user_dir):\n \"\"\"Test that encryption requires master password\"\"\"\n manager = SecureSettingsManager(temp_user_dir) # No password\n\n api_keys = APIKeys(openai=\"sk-test\")\n\n # Should raise EncryptionError\n with pytest.raises(EncryptionError) as exc_info:\n manager.save_api_keys(api_keys)\n\n # Check that the error is about encryption failure\n # The actual \"Master password not set\" is in the cause\n assert \"Failed to save encrypted API keys\" in str(exc_info.value) or \"Master password not set\" in str(exc_info.value)\n\n def test_wrong_password_fails_decryption(self, temp_user_dir):\n \"\"\"Test that wrong master password fails decryption\"\"\"\n # Save with one password\n manager1 = SecureSettingsManager(temp_user_dir, master_password=\"correct_password\")\n api_keys = APIKeys(openai=\"sk-test\")\n manager1.save_api_keys(api_keys)\n\n # Try to load with wrong password\n manager2 = SecureSettingsManager(temp_user_dir, master_password=\"wrong_password\")\n\n with pytest.raises(DecryptionError) as exc_info:\n manager2.load_api_keys()\n\n assert \"Failed to decrypt\" in str(exc_info.value)\n\n def test_update_api_key(self, secure_manager):\n \"\"\"Test updating a single API key\"\"\"\n # Update OpenAI key\n secure_manager.update_api_key(\"openai\", \"sk-new-openai-key\")\n\n # Verify\n loaded_keys = secure_manager.load_api_keys()\n assert loaded_keys.openai == \"sk-new-openai-key\"\n\n # Update Anthropic key\n secure_manager.update_api_key(\"anthropic\", \"sk-ant-new-key\")\n\n # Verify both keys exist\n loaded_keys = secure_manager.load_api_keys()\n assert loaded_keys.openai == \"sk-new-openai-key\"\n assert loaded_keys.anthropic == \"sk-ant-new-key\"\n\n def test_update_invalid_provider_raises_error(self, secure_manager):\n \"\"\"Test updating invalid provider raises ValueError\"\"\"\n with pytest.raises(ValueError) as exc_info:\n secure_manager.update_api_key(\"invalid_provider\", \"test-key\")\n\n assert \"Invalid provider\" in str(exc_info.value)\n\n def test_get_api_key(self, secure_manager):\n \"\"\"Test getting a single API key\"\"\"\n # Save keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n anthropic=\"sk-ant-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n # Get individual keys\n openai_key = secure_manager.get_api_key(\"openai\")\n anthropic_key = secure_manager.get_api_key(\"anthropic\")\n google_key = secure_manager.get_api_key(\"google\")\n\n assert openai_key == \"sk-openai-test\"\n assert anthropic_key == \"sk-ant-test\"\n assert google_key is None # Not set\n\n def test_delete_api_key(self, secure_manager):\n \"\"\"Test deleting a single API key\"\"\"\n # Save keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n anthropic=\"sk-ant-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n # Delete OpenAI key\n secure_manager.delete_api_key(\"openai\")\n\n # Verify\n loaded_keys = secure_manager.load_api_keys()\n assert loaded_keys.openai is None\n assert loaded_keys.anthropic == \"sk-ant-test\" # Still exists\n\n def test_clear_all_api_keys(self, secure_manager):\n \"\"\"Test clearing all API keys\"\"\"\n # Save keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n anthropic=\"sk-ant-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n assert secure_manager.api_keys_file.exists()\n\n # Clear all\n secure_manager.clear_all_api_keys()\n\n # Verify file deleted\n assert not secure_manager.api_keys_file.exists()\n\n def test_has_api_keys(self, secure_manager):\n \"\"\"Test has_api_keys() method\"\"\"\n # No keys initially\n assert secure_manager.has_api_keys() is False\n\n # Add keys\n api_keys = APIKeys(openai=\"sk-test\")\n secure_manager.save_api_keys(api_keys)\n\n # Should return True\n assert secure_manager.has_api_keys() is True\n\n # Clear keys\n secure_manager.clear_all_api_keys()\n\n # Should return False\n assert secure_manager.has_api_keys() is False\n\n def test_list_configured_providers(self, secure_manager):\n \"\"\"Test list_configured_providers() method\"\"\"\n # No keys initially\n providers = secure_manager.list_configured_providers()\n assert providers == []\n\n # Add some keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n google=\"google-test\",\n pinata_jwt=\"jwt-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n # List providers\n providers = secure_manager.list_configured_providers()\n\n assert \"openai\" in providers\n assert \"google\" in providers\n assert \"pinata_jwt\" in providers\n assert \"anthropic\" not in providers # Not set\n\n def test_encryption_creates_salt_file(self, secure_manager):\n \"\"\"Test that encryption creates salt file for new users\"\"\"\n # Save keys\n api_keys = APIKeys(openai=\"sk-test\")\n secure_manager.save_api_keys(api_keys)\n\n # Verify salt file created\n assert secure_manager.salt_file.exists()\n\n # Verify iterations file created (600K for new users)\n assert secure_manager.iterations_file.exists()\n iterations = int(secure_manager.iterations_file.read_text().strip())\n assert iterations == 600000\n\n def test_encryption_key_caching(self, secure_manager):\n \"\"\"Test that encryption key is cached for performance\"\"\"\n # First derivation\n key1 = secure_manager._derive_encryption_key()\n\n # Second derivation (should use cache)\n key2 = secure_manager._derive_encryption_key()\n\n # Should be same object (cached)\n assert key1 is key2\n\n def test_deterministic_encryption_key(self, temp_user_dir):\n \"\"\"Test that same password produces same encryption key\"\"\"\n # Create manager 1\n manager1 = SecureSettingsManager(temp_user_dir, master_password=\"password123\")\n key1 = manager1._derive_encryption_key()\n\n # Create manager 2 with same password (reusing salt)\n manager2 = SecureSettingsManager(temp_user_dir, master_password=\"password123\")\n key2 = manager2._derive_encryption_key()\n\n # Keys should be identical\n assert key1 == key2\n\n def test_different_passwords_produce_different_keys(self, temp_user_dir):\n \"\"\"Test that different passwords produce different keys\"\"\"\n # Create manager 1\n manager1 = SecureSettingsManager(temp_user_dir, master_password=\"password1\")\n key1 = manager1._derive_encryption_key()\n\n # Create manager 2 with different password\n manager2 = SecureSettingsManager(temp_user_dir, master_password=\"password2\")\n key2 = manager2._derive_encryption_key()\n\n # Keys should be different\n assert key1 != key2\n\n\nclass TestAPIKeyValidation:\n \"\"\"Test API key validation methods\"\"\"\n\n @pytest.fixture\n def secure_manager(self, tmp_path):\n \"\"\"Create test secure settings manager\"\"\"\n manager = SecureSettingsManager(tmp_path, master_password=\"test_password\")\n return manager\n\n @pytest.mark.asyncio\n async def test_validate_api_key_unsupported_provider(self, secure_manager):\n \"\"\"Test validation for unsupported provider\"\"\"\n result = await secure_manager.validate_api_key(\"unknown_provider\", \"test-key\")\n\n assert result[\"valid\"] is False\n assert \"not implemented\" in result[\"message\"].lower()\n\n # Note: Real API validation tests would require actual API keys or mocking\n # For now, we test the structure of validation responses\n\n @pytest.mark.asyncio\n async def test_validate_openai_structure(self, secure_manager):\n \"\"\"Test OpenAI validation returns correct structure\"\"\"\n # This will fail because it's an invalid key, but we can test the structure\n result = await secure_manager.validate_api_key(\"openai\", \"sk-invalid-test-key\")\n\n assert \"valid\" in result\n assert \"message\" in result\n assert \"details\" in result\n assert isinstance(result[\"valid\"], bool)\n assert isinstance(result[\"message\"], str)\n\n @pytest.mark.asyncio\n async def test_validation_handles_network_errors(self, secure_manager):\n \"\"\"Test that validation handles network errors gracefully\"\"\"\n # Use invalid URL format to trigger connection error\n result = await secure_manager.validate_api_key(\"openai\", \"\")\n\n assert result[\"valid\"] is False\n assert \"error\" in result[\"message\"].lower() or \"connection\" in result[\"message\"].lower()\n"}, "5fe2d171341c5f0cc644f95ec757ac8f9afcea3b": {"t": 1785768859.6959946, "n": 13164, "h": "9aeca824e3ed322d", "c": "\"\"\"\nTests for SecureSettingsManager (API Key Management)\n\nTests encrypted storage and validation of API keys.\n\"\"\"\n\nimport pytest\nimport tempfile\nimport shutil\nfrom pathlib import Path\n\nfrom config import APIKeys, SecureSettingsManager\nfrom core.exceptions import EncryptionError, DecryptionError\n\n\nclass TestAPIKeys:\n \"\"\"Test APIKeys dataclass\"\"\"\n\n def test_default_api_keys(self):\n \"\"\"Test default API keys (all None)\"\"\"\n keys = APIKeys()\n\n assert keys.openai is None\n assert keys.anthropic is None\n assert keys.google is None\n assert keys.deepseek is None\n assert keys.perplexity is None\n assert keys.pinata_jwt is None\n assert keys.elevenlabs is None\n assert keys.deepgram is None\n\n def test_custom_api_keys(self):\n \"\"\"Test custom API keys\"\"\"\n keys = APIKeys(\n openai=\"sk-test-openai\",\n anthropic=\"sk-ant-test\",\n google=\"test-google-key\"\n )\n\n assert keys.openai == \"sk-test-openai\"\n assert keys.anthropic == \"sk-ant-test\"\n assert keys.google == \"test-google-key\"\n assert keys.deepseek is None # Not set\n\n def test_to_dict_excludes_none(self):\n \"\"\"Test to_dict() excludes None values\"\"\"\n keys = APIKeys(\n openai=\"sk-test\",\n anthropic=None,\n google=\"google-test\"\n )\n\n keys_dict = keys.to_dict()\n\n assert \"openai\" in keys_dict\n assert \"google\" in keys_dict\n assert \"anthropic\" not in keys_dict # Excluded because None\n\n def test_is_empty(self):\n \"\"\"Test is_empty() method\"\"\"\n # Empty keys\n empty_keys = APIKeys()\n assert empty_keys.is_empty() is True\n\n # Non-empty keys\n non_empty_keys = APIKeys(openai=\"sk-test\")\n assert non_empty_keys.is_empty() is False\n\n\nclass TestSecureSettingsManager:\n \"\"\"Test SecureSettingsManager class\"\"\"\n\n @pytest.fixture\n def temp_user_dir(self):\n \"\"\"Create temporary user directory\"\"\"\n temp_dir = Path(tempfile.mkdtemp())\n yield temp_dir\n # Cleanup\n if temp_dir.exists():\n shutil.rmtree(temp_dir)\n\n @pytest.fixture\n def secure_manager(self, temp_user_dir):\n \"\"\"Create test secure settings manager\"\"\"\n manager = SecureSettingsManager(temp_user_dir, master_password=\"test_password\")\n return manager\n\n def test_initialization(self, temp_user_dir):\n \"\"\"Test secure settings manager initialization\"\"\"\n manager = SecureSettingsManager(temp_user_dir, master_password=\"test_pass\")\n\n assert manager.user_data_dir == temp_user_dir\n assert manager.user_data_dir.exists()\n assert manager.api_keys_file == temp_user_dir / \"api_keys.enc\"\n assert manager.salt_file == temp_user_dir / \"salt.bin\"\n\n def test_set_master_password(self, temp_user_dir):\n \"\"\"Test setting master password\"\"\"\n manager = SecureSettingsManager(temp_user_dir)\n assert manager._master_password is None\n\n manager.set_master_password(\"my_password\")\n assert manager._master_password == \"my_password\"\n\n def test_save_and_load_api_keys(self, secure_manager):\n \"\"\"Test saving and loading encrypted API keys\"\"\"\n # Create test API keys\n api_keys = APIKeys(\n openai=\"sk-test-openai-key\",\n anthropic=\"sk-ant-test-key\",\n google=\"google-test-key\"\n )\n\n # Save\n secure_manager.save_api_keys(api_keys)\n\n # Verify encrypted file exists\n assert secure_manager.api_keys_file.exists()\n\n # Load\n loaded_keys = secure_manager.load_api_keys()\n\n # Verify\n assert loaded_keys.openai == \"sk-test-openai-key\"\n assert loaded_keys.anthropic == \"sk-ant-test-key\"\n assert loaded_keys.google == \"google-test-key\"\n assert loaded_keys.deepseek is None\n\n def test_load_nonexistent_keys_returns_empty(self, secure_manager):\n \"\"\"Test loading API keys when file doesn't exist\"\"\"\n # File shouldn't exist yet\n assert not secure_manager.api_keys_file.exists()\n\n # Should return empty APIKeys\n loaded_keys = secure_manager.load_api_keys()\n\n assert loaded_keys.is_empty() is True\n\n def test_encryption_requires_master_password(self, temp_user_dir):\n \"\"\"Test that encryption requires master password\"\"\"\n manager = SecureSettingsManager(temp_user_dir) # No password\n\n api_keys = APIKeys(openai=\"sk-test\")\n\n # Should raise EncryptionError\n with pytest.raises(EncryptionError) as exc_info:\n manager.save_api_keys(api_keys)\n\n # Check that the error is about encryption failure\n # The actual \"Master password not set\" is in the cause\n assert \"Failed to save encrypted API keys\" in str(exc_info.value) or \"Master password not set\" in str(exc_info.value)\n\n def test_wrong_password_fails_decryption(self, temp_user_dir):\n \"\"\"Test that wrong master password fails decryption\"\"\"\n # Save with one password\n manager1 = SecureSettingsManager(temp_user_dir, master_password=\"correct_password\")\n api_keys = APIKeys(openai=\"sk-test\")\n manager1.save_api_keys(api_keys)\n\n # Try to load with wrong password\n manager2 = SecureSettingsManager(temp_user_dir, master_password=\"wrong_password\")\n\n with pytest.raises(DecryptionError) as exc_info:\n manager2.load_api_keys()\n\n assert \"Failed to decrypt\" in str(exc_info.value)\n\n def test_update_api_key(self, secure_manager):\n \"\"\"Test updating a single API key\"\"\"\n # Update OpenAI key\n secure_manager.update_api_key(\"openai\", \"sk-new-openai-key\")\n\n # Verify\n loaded_keys = secure_manager.load_api_keys()\n assert loaded_keys.openai == \"sk-new-openai-key\"\n\n # Update Anthropic key\n secure_manager.update_api_key(\"anthropic\", \"sk-ant-new-key\")\n\n # Verify both keys exist\n loaded_keys = secure_manager.load_api_keys()\n assert loaded_keys.openai == \"sk-new-openai-key\"\n assert loaded_keys.anthropic == \"sk-ant-new-key\"\n\n def test_update_invalid_provider_raises_error(self, secure_manager):\n \"\"\"Test updating invalid provider raises ValueError\"\"\"\n with pytest.raises(ValueError) as exc_info:\n secure_manager.update_api_key(\"invalid_provider\", \"test-key\")\n\n assert \"Invalid provider\" in str(exc_info.value)\n\n def test_get_api_key(self, secure_manager):\n \"\"\"Test getting a single API key\"\"\"\n # Save keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n anthropic=\"sk-ant-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n # Get individual keys\n openai_key = secure_manager.get_api_key(\"openai\")\n anthropic_key = secure_manager.get_api_key(\"anthropic\")\n google_key = secure_manager.get_api_key(\"google\")\n\n assert openai_key == \"sk-openai-test\"\n assert anthropic_key == \"sk-ant-test\"\n assert google_key is None # Not set\n\n def test_delete_api_key(self, secure_manager):\n \"\"\"Test deleting a single API key\"\"\"\n # Save keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n anthropic=\"sk-ant-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n # Delete OpenAI key\n secure_manager.delete_api_key(\"openai\")\n\n # Verify\n loaded_keys = secure_manager.load_api_keys()\n assert loaded_keys.openai is None\n assert loaded_keys.anthropic == \"sk-ant-test\" # Still exists\n\n def test_clear_all_api_keys(self, secure_manager):\n \"\"\"Test clearing all API keys\"\"\"\n # Save keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n anthropic=\"sk-ant-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n assert secure_manager.api_keys_file.exists()\n\n # Clear all\n secure_manager.clear_all_api_keys()\n\n # Verify file deleted\n assert not secure_manager.api_keys_file.exists()\n\n def test_has_api_keys(self, secure_manager):\n \"\"\"Test has_api_keys() method\"\"\"\n # No keys initially\n assert secure_manager.has_api_keys() is False\n\n # Add keys\n api_keys = APIKeys(openai=\"sk-test\")\n secure_manager.save_api_keys(api_keys)\n\n # Should return True\n assert secure_manager.has_api_keys() is True\n\n # Clear keys\n secure_manager.clear_all_api_keys()\n\n # Should return False\n assert secure_manager.has_api_keys() is False\n\n def test_list_configured_providers(self, secure_manager):\n \"\"\"Test list_configured_providers() method\"\"\"\n # No keys initially\n providers = secure_manager.list_configured_providers()\n assert providers == []\n\n # Add some keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n google=\"google-test\",\n pinata_jwt=\"jwt-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n # List providers\n providers = secure_manager.list_configured_providers()\n\n assert \"openai\" in providers\n assert \"google\" in providers\n assert \"pinata_jwt\" in providers\n assert \"anthropic\" not in providers # Not set\n\n def test_encryption_creates_salt_file(self, secure_manager):\n \"\"\"Test that encryption creates salt file for new users\"\"\"\n # Save keys\n api_keys = APIKeys(openai=\"sk-test\")\n secure_manager.save_api_keys(api_keys)\n\n # Verify salt file created\n assert secure_manager.salt_file.exists()\n\n # Verify iterations file created (600K for new users)\n assert secure_manager.iterations_file.exists()\n iterations = int(secure_manager.iterations_file.read_text().strip())\n assert iterations == 600000\n\n def test_encryption_key_caching(self, secure_manager):\n \"\"\"Test that encryption key is cached for performance\"\"\"\n # First derivation\n key1 = secure_manager._derive_encryption_key()\n\n # Second derivation (should use cache)\n key2 = secure_manager._derive_encryption_key()\n\n # Should be same object (cached)\n assert key1 is key2\n\n def test_deterministic_encryption_key(self, temp_user_dir):\n \"\"\"Test that same password produces same encryption key\"\"\"\n # Create manager 1\n manager1 = SecureSettingsManager(temp_user_dir, master_password=\"password123\")\n key1 = manager1._derive_encryption_key()\n\n # Create manager 2 with same password (reusing salt)\n manager2 = SecureSettingsManager(temp_user_dir, master_password=\"password123\")\n key2 = manager2._derive_encryption_key()\n\n # Keys should be identical\n assert key1 == key2\n\n def test_different_passwords_produce_different_keys(self, temp_user_dir):\n \"\"\"Test that different passwords produce different keys\"\"\"\n # Create manager 1\n manager1 = SecureSettingsManager(temp_user_dir, master_password=\"password1\")\n key1 = manager1._derive_encryption_key()\n\n # Create manager 2 with different password\n manager2 = SecureSettingsManager(temp_user_dir, master_password=\"password2\")\n key2 = manager2._derive_encryption_key()\n\n # Keys should be different\n assert key1 != key2\n\n\nclass TestAPIKeyValidation:\n \"\"\"Test API key validation methods\"\"\"\n\n @pytest.fixture\n def secure_manager(self, tmp_path):\n \"\"\"Create test secure settings manager\"\"\"\n manager = SecureSettingsManager(tmp_path, master_password=\"test_password\")\n return manager\n\n @pytest.mark.asyncio\n async def test_validate_api_key_unsupported_provider(self, secure_manager):\n \"\"\"Test validation for unsupported provider\"\"\"\n result = await secure_manager.validate_api_key(\"unknown_provider\", \"test-key\")\n\n assert result[\"valid\"] is False\n assert \"not implemented\" in result[\"message\"].lower()\n\n # Note: Real API validation tests would require actual API keys or mocking\n # For now, we test the structure of validation responses\n\n @pytest.mark.asyncio\n async def test_validate_openai_structure(self, secure_manager):\n \"\"\"Test OpenAI validation returns correct structure\"\"\"\n # This will fail because it's an invalid key, but we can test the structure\n result = await secure_manager.validate_api_key(\"openai\", \"sk-invalid-test-key\")\n\n assert \"valid\" in result\n assert \"message\" in result\n assert \"details\" in result\n assert isinstance(result[\"valid\"], bool)\n assert isinstance(result[\"message\"], str)\n\n @pytest.mark.asyncio\n async def test_validation_handles_network_errors(self, secure_manager):\n \"\"\"Test that validation handles network errors gracefully\"\"\"\n # Use invalid URL format to trigger connection error\n result = await secure_manager.validate_api_key(\"openai\", \"\")\n\n assert result[\"valid\"] is False\n assert \"error\" in result[\"message\"].lower() or \"connection\" in result[\"message\"].lower()\n"}, "268d382eeee29d2b2829cdb69fd17fe554d66eed": {"t": 1786068520.760223, "n": 13164, "h": "9aeca824e3ed322d", "c": "\"\"\"\nTests for SecureSettingsManager (API Key Management)\n\nTests encrypted storage and validation of API keys.\n\"\"\"\n\nimport pytest\nimport tempfile\nimport shutil\nfrom pathlib import Path\n\nfrom config import APIKeys, SecureSettingsManager\nfrom core.exceptions import EncryptionError, DecryptionError\n\n\nclass TestAPIKeys:\n \"\"\"Test APIKeys dataclass\"\"\"\n\n def test_default_api_keys(self):\n \"\"\"Test default API keys (all None)\"\"\"\n keys = APIKeys()\n\n assert keys.openai is None\n assert keys.anthropic is None\n assert keys.google is None\n assert keys.deepseek is None\n assert keys.perplexity is None\n assert keys.pinata_jwt is None\n assert keys.elevenlabs is None\n assert keys.deepgram is None\n\n def test_custom_api_keys(self):\n \"\"\"Test custom API keys\"\"\"\n keys = APIKeys(\n openai=\"sk-test-openai\",\n anthropic=\"sk-ant-test\",\n google=\"test-google-key\"\n )\n\n assert keys.openai == \"sk-test-openai\"\n assert keys.anthropic == \"sk-ant-test\"\n assert keys.google == \"test-google-key\"\n assert keys.deepseek is None # Not set\n\n def test_to_dict_excludes_none(self):\n \"\"\"Test to_dict() excludes None values\"\"\"\n keys = APIKeys(\n openai=\"sk-test\",\n anthropic=None,\n google=\"google-test\"\n )\n\n keys_dict = keys.to_dict()\n\n assert \"openai\" in keys_dict\n assert \"google\" in keys_dict\n assert \"anthropic\" not in keys_dict # Excluded because None\n\n def test_is_empty(self):\n \"\"\"Test is_empty() method\"\"\"\n # Empty keys\n empty_keys = APIKeys()\n assert empty_keys.is_empty() is True\n\n # Non-empty keys\n non_empty_keys = APIKeys(openai=\"sk-test\")\n assert non_empty_keys.is_empty() is False\n\n\nclass TestSecureSettingsManager:\n \"\"\"Test SecureSettingsManager class\"\"\"\n\n @pytest.fixture\n def temp_user_dir(self):\n \"\"\"Create temporary user directory\"\"\"\n temp_dir = Path(tempfile.mkdtemp())\n yield temp_dir\n # Cleanup\n if temp_dir.exists():\n shutil.rmtree(temp_dir)\n\n @pytest.fixture\n def secure_manager(self, temp_user_dir):\n \"\"\"Create test secure settings manager\"\"\"\n manager = SecureSettingsManager(temp_user_dir, master_password=\"test_password\")\n return manager\n\n def test_initialization(self, temp_user_dir):\n \"\"\"Test secure settings manager initialization\"\"\"\n manager = SecureSettingsManager(temp_user_dir, master_password=\"test_pass\")\n\n assert manager.user_data_dir == temp_user_dir\n assert manager.user_data_dir.exists()\n assert manager.api_keys_file == temp_user_dir / \"api_keys.enc\"\n assert manager.salt_file == temp_user_dir / \"salt.bin\"\n\n def test_set_master_password(self, temp_user_dir):\n \"\"\"Test setting master password\"\"\"\n manager = SecureSettingsManager(temp_user_dir)\n assert manager._master_password is None\n\n manager.set_master_password(\"my_password\")\n assert manager._master_password == \"my_password\"\n\n def test_save_and_load_api_keys(self, secure_manager):\n \"\"\"Test saving and loading encrypted API keys\"\"\"\n # Create test API keys\n api_keys = APIKeys(\n openai=\"sk-test-openai-key\",\n anthropic=\"sk-ant-test-key\",\n google=\"google-test-key\"\n )\n\n # Save\n secure_manager.save_api_keys(api_keys)\n\n # Verify encrypted file exists\n assert secure_manager.api_keys_file.exists()\n\n # Load\n loaded_keys = secure_manager.load_api_keys()\n\n # Verify\n assert loaded_keys.openai == \"sk-test-openai-key\"\n assert loaded_keys.anthropic == \"sk-ant-test-key\"\n assert loaded_keys.google == \"google-test-key\"\n assert loaded_keys.deepseek is None\n\n def test_load_nonexistent_keys_returns_empty(self, secure_manager):\n \"\"\"Test loading API keys when file doesn't exist\"\"\"\n # File shouldn't exist yet\n assert not secure_manager.api_keys_file.exists()\n\n # Should return empty APIKeys\n loaded_keys = secure_manager.load_api_keys()\n\n assert loaded_keys.is_empty() is True\n\n def test_encryption_requires_master_password(self, temp_user_dir):\n \"\"\"Test that encryption requires master password\"\"\"\n manager = SecureSettingsManager(temp_user_dir) # No password\n\n api_keys = APIKeys(openai=\"sk-test\")\n\n # Should raise EncryptionError\n with pytest.raises(EncryptionError) as exc_info:\n manager.save_api_keys(api_keys)\n\n # Check that the error is about encryption failure\n # The actual \"Master password not set\" is in the cause\n assert \"Failed to save encrypted API keys\" in str(exc_info.value) or \"Master password not set\" in str(exc_info.value)\n\n def test_wrong_password_fails_decryption(self, temp_user_dir):\n \"\"\"Test that wrong master password fails decryption\"\"\"\n # Save with one password\n manager1 = SecureSettingsManager(temp_user_dir, master_password=\"correct_password\")\n api_keys = APIKeys(openai=\"sk-test\")\n manager1.save_api_keys(api_keys)\n\n # Try to load with wrong password\n manager2 = SecureSettingsManager(temp_user_dir, master_password=\"wrong_password\")\n\n with pytest.raises(DecryptionError) as exc_info:\n manager2.load_api_keys()\n\n assert \"Failed to decrypt\" in str(exc_info.value)\n\n def test_update_api_key(self, secure_manager):\n \"\"\"Test updating a single API key\"\"\"\n # Update OpenAI key\n secure_manager.update_api_key(\"openai\", \"sk-new-openai-key\")\n\n # Verify\n loaded_keys = secure_manager.load_api_keys()\n assert loaded_keys.openai == \"sk-new-openai-key\"\n\n # Update Anthropic key\n secure_manager.update_api_key(\"anthropic\", \"sk-ant-new-key\")\n\n # Verify both keys exist\n loaded_keys = secure_manager.load_api_keys()\n assert loaded_keys.openai == \"sk-new-openai-key\"\n assert loaded_keys.anthropic == \"sk-ant-new-key\"\n\n def test_update_invalid_provider_raises_error(self, secure_manager):\n \"\"\"Test updating invalid provider raises ValueError\"\"\"\n with pytest.raises(ValueError) as exc_info:\n secure_manager.update_api_key(\"invalid_provider\", \"test-key\")\n\n assert \"Invalid provider\" in str(exc_info.value)\n\n def test_get_api_key(self, secure_manager):\n \"\"\"Test getting a single API key\"\"\"\n # Save keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n anthropic=\"sk-ant-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n # Get individual keys\n openai_key = secure_manager.get_api_key(\"openai\")\n anthropic_key = secure_manager.get_api_key(\"anthropic\")\n google_key = secure_manager.get_api_key(\"google\")\n\n assert openai_key == \"sk-openai-test\"\n assert anthropic_key == \"sk-ant-test\"\n assert google_key is None # Not set\n\n def test_delete_api_key(self, secure_manager):\n \"\"\"Test deleting a single API key\"\"\"\n # Save keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n anthropic=\"sk-ant-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n # Delete OpenAI key\n secure_manager.delete_api_key(\"openai\")\n\n # Verify\n loaded_keys = secure_manager.load_api_keys()\n assert loaded_keys.openai is None\n assert loaded_keys.anthropic == \"sk-ant-test\" # Still exists\n\n def test_clear_all_api_keys(self, secure_manager):\n \"\"\"Test clearing all API keys\"\"\"\n # Save keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n anthropic=\"sk-ant-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n assert secure_manager.api_keys_file.exists()\n\n # Clear all\n secure_manager.clear_all_api_keys()\n\n # Verify file deleted\n assert not secure_manager.api_keys_file.exists()\n\n def test_has_api_keys(self, secure_manager):\n \"\"\"Test has_api_keys() method\"\"\"\n # No keys initially\n assert secure_manager.has_api_keys() is False\n\n # Add keys\n api_keys = APIKeys(openai=\"sk-test\")\n secure_manager.save_api_keys(api_keys)\n\n # Should return True\n assert secure_manager.has_api_keys() is True\n\n # Clear keys\n secure_manager.clear_all_api_keys()\n\n # Should return False\n assert secure_manager.has_api_keys() is False\n\n def test_list_configured_providers(self, secure_manager):\n \"\"\"Test list_configured_providers() method\"\"\"\n # No keys initially\n providers = secure_manager.list_configured_providers()\n assert providers == []\n\n # Add some keys\n api_keys = APIKeys(\n openai=\"sk-openai-test\",\n google=\"google-test\",\n pinata_jwt=\"jwt-test\"\n )\n secure_manager.save_api_keys(api_keys)\n\n # List providers\n providers = secure_manager.list_configured_providers()\n\n assert \"openai\" in providers\n assert \"google\" in providers\n assert \"pinata_jwt\" in providers\n assert \"anthropic\" not in providers # Not set\n\n def test_encryption_creates_salt_file(self, secure_manager):\n \"\"\"Test that encryption creates salt file for new users\"\"\"\n # Save keys\n api_keys = APIKeys(openai=\"sk-test\")\n secure_manager.save_api_keys(api_keys)\n\n # Verify salt file created\n assert secure_manager.salt_file.exists()\n\n # Verify iterations file created (600K for new users)\n assert secure_manager.iterations_file.exists()\n iterations = int(secure_manager.iterations_file.read_text().strip())\n assert iterations == 600000\n\n def test_encryption_key_caching(self, secure_manager):\n \"\"\"Test that encryption key is cached for performance\"\"\"\n # First derivation\n key1 = secure_manager._derive_encryption_key()\n\n # Second derivation (should use cache)\n key2 = secure_manager._derive_encryption_key()\n\n # Should be same object (cached)\n assert key1 is key2\n\n def test_deterministic_encryption_key(self, temp_user_dir):\n \"\"\"Test that same password produces same encryption key\"\"\"\n # Create manager 1\n manager1 = SecureSettingsManager(temp_user_dir, master_password=\"password123\")\n key1 = manager1._derive_encryption_key()\n\n # Create manager 2 with same password (reusing salt)\n manager2 = SecureSettingsManager(temp_user_dir, master_password=\"password123\")\n key2 = manager2._derive_encryption_key()\n\n # Keys should be identical\n assert key1 == key2\n\n def test_different_passwords_produce_different_keys(self, temp_user_dir):\n \"\"\"Test that different passwords produce different keys\"\"\"\n # Create manager 1\n manager1 = SecureSettingsManager(temp_user_dir, master_password=\"password1\")\n key1 = manager1._derive_encryption_key()\n\n # Create manager 2 with different password\n manager2 = SecureSettingsManager(temp_user_dir, master_password=\"password2\")\n key2 = manager2._derive_encryption_key()\n\n # Keys should be different\n assert key1 != key2\n\n\nclass TestAPIKeyValidation:\n \"\"\"Test API key validation methods\"\"\"\n\n @pytest.fixture\n def secure_manager(self, tmp_path):\n \"\"\"Create test secure settings manager\"\"\"\n manager = SecureSettingsManager(tmp_path, master_password=\"test_password\")\n return manager\n\n @pytest.mark.asyncio\n async def test_validate_api_key_unsupported_provider(self, secure_manager):\n \"\"\"Test validation for unsupported provider\"\"\"\n result = await secure_manager.validate_api_key(\"unknown_provider\", \"test-key\")\n\n assert result[\"valid\"] is False\n assert \"not implemented\" in result[\"message\"].lower()\n\n # Note: Real API validation tests would require actual API keys or mocking\n # For now, we test the structure of validation responses\n\n @pytest.mark.asyncio\n async def test_validate_openai_structure(self, secure_manager):\n \"\"\"Test OpenAI validation returns correct structure\"\"\"\n # This will fail because it's an invalid key, but we can test the structure\n result = await secure_manager.validate_api_key(\"openai\", \"sk-invalid-test-key\")\n\n assert \"valid\" in result\n assert \"message\" in result\n assert \"details\" in result\n assert isinstance(result[\"valid\"], bool)\n assert isinstance(result[\"message\"], str)\n\n @pytest.mark.asyncio\n async def test_validation_handles_network_errors(self, secure_manager):\n \"\"\"Test that validation handles network errors gracefully\"\"\"\n # Use invalid URL format to trigger connection error\n result = await secure_manager.validate_api_key(\"openai\", \"\")\n\n assert result[\"valid\"] is False\n assert \"error\" in result[\"message\"].lower() or \"connection\" in result[\"message\"].lower()\n"}}}} |