from __future__ import annotations from unittest.mock import patch from asgiref.sync import async_to_sync from django.test import TestCase from core.messaging.ai import run_prompt from core.models import AI, AIRunLog, User class _FakeResponseMessage: def __init__(self, content): self.content = content class _FakeChoice: def __init__(self, content): self.message = _FakeResponseMessage(content) class _FakeResponse: def __init__(self, content): self.choices = [_FakeChoice(content)] class _FakeCompletionsOK: async def create(self, **kwargs): return _FakeResponse("ok-output") class _FakeChatOK: def __init__(self): self.completions = _FakeCompletionsOK() class _FakeClientOK: def __init__(self, **kwargs): self.chat = _FakeChatOK() class _FakeCompletionsFail: async def create(self, **kwargs): raise RuntimeError("provider boom") class _FakeChatFail: def __init__(self): self.completions = _FakeCompletionsFail() class _FakeClientFail: def __init__(self, **kwargs): self.chat = _FakeChatFail() class AIRunLogTests(TestCase): def setUp(self): self.user = User.objects.create_user( username="ai-log-user", email="ai-log@example.com", password="x", ) self.ai = AI.objects.create( user=self.user, base_url="https://example.invalid", api_key="test-key", model="gpt-4o-mini", ) self.prompt = [{"role": "user", "content": "Hello world"}] def test_run_prompt_logs_success(self): with patch("core.messaging.ai.AsyncOpenAI", _FakeClientOK): out = async_to_sync(run_prompt)(self.prompt, self.ai, operation="test_ok") self.assertEqual("ok-output", out) row = AIRunLog.objects.order_by("-id").first() self.assertIsNotNone(row) self.assertEqual("ok", row.status) self.assertEqual("test_ok", row.operation) self.assertEqual(1, row.message_count) self.assertEqual(len("Hello world"), row.prompt_chars) self.assertEqual(len("ok-output"), row.response_chars) self.assertTrue((row.duration_ms or 0) >= 0) def test_run_prompt_logs_failure(self): with patch("core.messaging.ai.AsyncOpenAI", _FakeClientFail): with self.assertRaises(RuntimeError): async_to_sync(run_prompt)(self.prompt, self.ai, operation="test_fail") row = AIRunLog.objects.order_by("-id").first() self.assertIsNotNone(row) self.assertEqual("failed", row.status) self.assertEqual("test_fail", row.operation) self.assertIn("provider boom", row.error)