test_api_check.py 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. """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"""
  2. import asyncio
  3. import unittest
  4. from types import SimpleNamespace
  5. from unittest import mock
  6. import anthropic
  7. from bot.claude_client import ClaudeClient
  8. from bot.discord_bot import ChatBot
  9. def fake_client(retrieve) -> ClaudeClient:
  10. # Sans __init__ : pas de client Anthropic à créer.
  11. client = ClaudeClient.__new__(ClaudeClient)
  12. client.settings = SimpleNamespace(api_timeout=0.2, model="claude-opus-5-5")
  13. client._client = SimpleNamespace(models=SimpleNamespace(retrieve=retrieve))
  14. return client
  15. def api_error(cls, status: int):
  16. response = mock.Mock(status_code=status, headers={})
  17. return cls("erreur", response=response, body={"error": {"message": "détail de l'API"}})
  18. class CheckAccessTest(unittest.IsolatedAsyncioTestCase):
  19. async def check(self, error):
  20. return await fake_client(mock.AsyncMock(side_effect=error)).check_access()
  21. async def test_valid_key_and_model(self):
  22. self.assertIsNone(await fake_client(mock.AsyncMock()).check_access())
  23. async def test_invalid_key(self):
  24. self.assertIn("ANTHROPIC_API_KEY", await self.check(api_error(anthropic.AuthenticationError, 401)))
  25. async def test_permission_denied(self):
  26. self.assertIn("détail de l'API", await self.check(api_error(anthropic.PermissionDeniedError, 403)))
  27. async def test_unknown_model(self):
  28. problem = await self.check(api_error(anthropic.NotFoundError, 404))
  29. self.assertIn("CLAUDE_MODEL", problem)
  30. self.assertIn("claude-opus-5-5", problem)
  31. async def test_api_down_does_not_block(self):
  32. # Panne passagère : on démarre quand même, avec un avertissement.
  33. for error in (api_error(anthropic.InternalServerError, 500),
  34. anthropic.APIConnectionError(request=mock.Mock())):
  35. with self.subTest(error=type(error).__name__), self.assertLogs("chatbot.claude", "WARNING"):
  36. self.assertIsNone(await self.check(error))
  37. async def test_slow_api_does_not_block(self):
  38. async def slow(_):
  39. await asyncio.sleep(10)
  40. with self.assertLogs("chatbot.claude", "WARNING") as logs:
  41. self.assertIsNone(await fake_client(slow).check_access())
  42. self.assertIn("délai dépassé", logs.output[0])
  43. class SetupHookTest(unittest.IsolatedAsyncioTestCase):
  44. async def test_bad_key_stops_bot(self):
  45. bot = mock.Mock(config_error=None)
  46. bot.claude.check_access = mock.AsyncMock(return_value="clé API Anthropic invalide")
  47. bot.close = mock.AsyncMock()
  48. with self.assertLogs("chatbot", "CRITICAL"):
  49. await ChatBot.setup_hook(bot)
  50. self.assertEqual(bot.config_error, "clé API Anthropic invalide")
  51. bot.close.assert_awaited_once()
  52. bot.tree.add_command.assert_not_called()
  53. if __name__ == "__main__":
  54. unittest.main()