personal memory agent
at main 209 lines 7.2 kB view raw
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4from __future__ import annotations 5 6import json 7from unittest.mock import Mock, patch 8 9import pytest 10 11import think.providers 12import think.providers.anthropic 13import think.providers.google 14import think.providers.openai 15from convey import create_app 16from tests.conftest import copytree_tracked 17from think.providers import validate_key 18 19 20@pytest.fixture 21def settings_client(journal_copy): 22 app = create_app(str(journal_copy)) 23 app.config["TESTING"] = True 24 return app.test_client(), journal_copy 25 26 27def test_validate_key_anthropic_success(): 28 client = Mock() 29 client.models.list.return_value = [Mock()] 30 31 with patch("anthropic.Anthropic", return_value=client) as mock_cls: 32 result = think.providers.anthropic.validate_key("test-key") 33 34 assert result == {"valid": True} 35 mock_cls.assert_called_once_with(api_key="test-key", timeout=10) 36 37 38def test_validate_key_anthropic_auth_error(): 39 client = Mock() 40 client.models.list.side_effect = Exception("invalid x-api-key") 41 42 with patch("anthropic.Anthropic", return_value=client): 43 result = think.providers.anthropic.validate_key("bad-key") 44 45 assert result["valid"] is False 46 assert "invalid x-api-key" in result["error"] 47 48 49def test_validate_key_openai_success(): 50 client = Mock() 51 client.models.list.return_value = [Mock()] 52 53 with patch("openai.OpenAI", return_value=client) as mock_cls: 54 result = think.providers.openai.validate_key("test-key") 55 56 assert result == {"valid": True} 57 mock_cls.assert_called_once_with(api_key="test-key", timeout=10) 58 59 60def test_validate_key_openai_auth_error(): 61 client = Mock() 62 client.models.list.side_effect = Exception("Incorrect API key") 63 64 with patch("openai.OpenAI", return_value=client): 65 result = think.providers.openai.validate_key("bad-key") 66 67 assert result["valid"] is False 68 assert "Incorrect API key" in result["error"] 69 70 71def test_validate_key_google_success(): 72 client = Mock() 73 client.models.list.return_value = [Mock()] 74 75 with patch("think.providers.google.genai.Client", return_value=client) as mock_cls: 76 result = think.providers.google.validate_key("test-key") 77 78 assert result == {"valid": True} 79 mock_cls.assert_called_once() 80 assert mock_cls.call_args.kwargs["api_key"] == "test-key" 81 82 83def test_validate_key_google_auth_error(): 84 client = Mock() 85 client.models.list.side_effect = Exception("API key not valid") 86 87 with patch("think.providers.google.genai.Client", return_value=client): 88 result = think.providers.google.validate_key("bad-key") 89 90 assert result["valid"] is False 91 assert "API key not valid" in result["error"] 92 93 94def test_validate_key_dispatcher_success(): 95 with patch("think.providers.google.validate_key", return_value={"valid": True}): 96 result = validate_key("google", "test-key") 97 98 assert result == {"valid": True} 99 100 101def test_validate_key_dispatcher_unknown_provider(): 102 with pytest.raises(ValueError, match="Unknown provider"): 103 validate_key("bogus", "test-key") 104 105 106def test_validate_key_timeout(): 107 """Validate that timeout exceptions are caught and reported.""" 108 client = Mock() 109 client.models.list.side_effect = TimeoutError("Connection timed out") 110 111 with patch("openai.OpenAI", return_value=client): 112 result = think.providers.openai.validate_key("test-key") 113 114 assert result["valid"] is False 115 assert "timed out" in result["error"] 116 117 118def test_update_config_saves_key_validation(settings_client): 119 client, journal = settings_client 120 121 with patch( 122 "think.providers.validate_key", 123 return_value={"valid": False, "error": "bad key"}, 124 ): 125 response = client.put( 126 "/app/settings/api/config", 127 json={"section": "env", "data": {"GOOGLE_API_KEY": "bad-key"}}, 128 ) 129 130 assert response.status_code == 200 131 payload = response.get_json() 132 assert payload["success"] is True 133 assert payload["key_validation"]["google"]["valid"] is False 134 assert payload["key_validation"]["google"]["error"] == "bad key" 135 assert "timestamp" in payload["key_validation"]["google"] 136 137 config = json.loads((journal / "config" / "journal.json").read_text()) 138 assert config["providers"]["auth"]["google"] == "api_key" 139 assert config["providers"]["key_validation"]["google"]["valid"] is False 140 141 142def test_update_config_clears_key_validation(settings_client): 143 client, journal = settings_client 144 config_path = journal / "config" / "journal.json" 145 config = json.loads(config_path.read_text()) 146 config.setdefault("env", {})["GOOGLE_API_KEY"] = "existing-key" 147 config.setdefault("providers", {}).setdefault("auth", {})["google"] = "api_key" 148 config["providers"]["key_validation"] = { 149 "google": {"valid": True, "timestamp": "2026-01-01T00:00:00+00:00"} 150 } 151 config_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8") 152 153 response = client.put( 154 "/app/settings/api/config", 155 json={"section": "env", "data": {"GOOGLE_API_KEY": ""}}, 156 ) 157 158 assert response.status_code == 200 159 payload = response.get_json() 160 assert payload["success"] is True 161 assert "google" not in payload["key_validation"] 162 163 saved = json.loads(config_path.read_text()) 164 assert saved["providers"]["auth"]["google"] == "platform" 165 assert "google" not in saved["providers"]["key_validation"] 166 167 168def test_get_providers_includes_key_validation(settings_client): 169 client, journal = settings_client 170 config_path = journal / "config" / "journal.json" 171 config = json.loads(config_path.read_text()) 172 config.setdefault("providers", {})["key_validation"] = { 173 "openai": {"valid": True, "timestamp": "2026-01-01T00:00:00+00:00"} 174 } 175 config_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8") 176 177 response = client.get("/app/settings/api/providers") 178 179 assert response.status_code == 200 180 payload = response.get_json() 181 assert payload["key_validation"]["openai"]["valid"] is True 182 183 184def test_validate_all_keys_endpoint(settings_client): 185 client, journal = settings_client 186 config_path = journal / "config" / "journal.json" 187 config = json.loads(config_path.read_text()) 188 config.setdefault("env", {})["GOOGLE_API_KEY"] = "google-key" 189 config["env"]["OPENAI_API_KEY"] = "openai-key" 190 config_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8") 191 192 def fake_validate(provider: str, api_key: str) -> dict: 193 return { 194 "valid": provider == "google", 195 "error": "" if provider == "google" else "bad key", 196 } 197 198 with patch("think.providers.validate_key", side_effect=fake_validate): 199 response = client.post("/app/settings/api/validate-keys") 200 201 assert response.status_code == 200 202 payload = response.get_json() 203 assert payload["success"] is True 204 assert payload["key_validation"]["google"]["valid"] is True 205 assert payload["key_validation"]["openai"]["valid"] is False 206 assert "timestamp" in payload["key_validation"]["google"] 207 208 saved = json.loads(config_path.read_text()) 209 assert set(saved["providers"]["key_validation"]) == {"google", "openai"}