|
|
@@ -0,0 +1,74 @@
|
|
|
+"""Tests de la vérification de la clé API et du modèle au démarrage. Lancer depuis la racine du projet : python -m unittest"""
|
|
|
+
|
|
|
+import asyncio
|
|
|
+import unittest
|
|
|
+from types import SimpleNamespace
|
|
|
+from unittest import mock
|
|
|
+
|
|
|
+import anthropic
|
|
|
+
|
|
|
+from bot.claude_client import ClaudeClient
|
|
|
+from bot.discord_bot import ChatBot
|
|
|
+
|
|
|
+
|
|
|
+def fake_client(retrieve) -> ClaudeClient:
|
|
|
+ # Sans __init__ : pas de client Anthropic à créer.
|
|
|
+ client = ClaudeClient.__new__(ClaudeClient)
|
|
|
+ client.settings = SimpleNamespace(api_timeout=0.2, model="claude-opus-5-5")
|
|
|
+ client._client = SimpleNamespace(models=SimpleNamespace(retrieve=retrieve))
|
|
|
+ return client
|
|
|
+
|
|
|
+
|
|
|
+def api_error(cls, status: int):
|
|
|
+ response = mock.Mock(status_code=status, headers={})
|
|
|
+ return cls("erreur", response=response, body={"error": {"message": "détail de l'API"}})
|
|
|
+
|
|
|
+
|
|
|
+class CheckAccessTest(unittest.IsolatedAsyncioTestCase):
|
|
|
+ async def check(self, error):
|
|
|
+ return await fake_client(mock.AsyncMock(side_effect=error)).check_access()
|
|
|
+
|
|
|
+ async def test_valid_key_and_model(self):
|
|
|
+ self.assertIsNone(await fake_client(mock.AsyncMock()).check_access())
|
|
|
+
|
|
|
+ async def test_invalid_key(self):
|
|
|
+ self.assertIn("ANTHROPIC_API_KEY", await self.check(api_error(anthropic.AuthenticationError, 401)))
|
|
|
+
|
|
|
+ async def test_permission_denied(self):
|
|
|
+ self.assertIn("détail de l'API", await self.check(api_error(anthropic.PermissionDeniedError, 403)))
|
|
|
+
|
|
|
+ async def test_unknown_model(self):
|
|
|
+ problem = await self.check(api_error(anthropic.NotFoundError, 404))
|
|
|
+ self.assertIn("CLAUDE_MODEL", problem)
|
|
|
+ self.assertIn("claude-opus-5-5", problem)
|
|
|
+
|
|
|
+ async def test_api_down_does_not_block(self):
|
|
|
+ # Panne passagère : on démarre quand même, avec un avertissement.
|
|
|
+ for error in (api_error(anthropic.InternalServerError, 500),
|
|
|
+ anthropic.APIConnectionError(request=mock.Mock())):
|
|
|
+ with self.subTest(error=type(error).__name__), self.assertLogs("chatbot.claude", "WARNING"):
|
|
|
+ self.assertIsNone(await self.check(error))
|
|
|
+
|
|
|
+ async def test_slow_api_does_not_block(self):
|
|
|
+ async def slow(_):
|
|
|
+ await asyncio.sleep(10)
|
|
|
+
|
|
|
+ with self.assertLogs("chatbot.claude", "WARNING") as logs:
|
|
|
+ self.assertIsNone(await fake_client(slow).check_access())
|
|
|
+ self.assertIn("délai dépassé", logs.output[0])
|
|
|
+
|
|
|
+
|
|
|
+class SetupHookTest(unittest.IsolatedAsyncioTestCase):
|
|
|
+ async def test_bad_key_stops_bot(self):
|
|
|
+ bot = mock.Mock(config_error=None)
|
|
|
+ bot.claude.check_access = mock.AsyncMock(return_value="clé API Anthropic invalide")
|
|
|
+ bot.close = mock.AsyncMock()
|
|
|
+ with self.assertLogs("chatbot", "CRITICAL"):
|
|
|
+ await ChatBot.setup_hook(bot)
|
|
|
+ self.assertEqual(bot.config_error, "clé API Anthropic invalide")
|
|
|
+ bot.close.assert_awaited_once()
|
|
|
+ bot.tree.add_command.assert_not_called()
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ unittest.main()
|