Files
zzrouter/backend/tests/test_logger.py
tech d080e67c29 test: add comprehensive test suite with 89% coverage
- Add tests for all backend modules: config, database, models, services,
  routes, middleware, and providers
- Separate dev dependencies using dependency-groups
- Update CLAUDE.md with test commands and project structure
- Add .coverage and .pytest_cache to gitignore

88 tests covering:
- Authentication and authorization
- Model routing and key selection
- Async logging with batch processing
- All API endpoints (chat, openai, anthropic, health)
- LiteLLM wrapper (mocked external calls)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:12:57 +08:00

159 lines
4.4 KiB
Python

"""Tests for async logger service."""
import pytest
import asyncio
from unittest.mock import patch, AsyncMock, MagicMock
from app.services.logger import AsyncLogger, LogEntry, async_logger
class TestLogEntry:
"""Test LogEntry dataclass."""
def test_create_log_entry(self):
"""Test creating a LogEntry."""
entry = LogEntry(
client_key_id=1,
provider_id=2,
model="gpt-4o",
prompt_tokens=10,
completion_tokens=20,
latency_ms=100,
success=True
)
assert entry.client_key_id == 1
assert entry.model == "gpt-4o"
assert entry.success is True
def test_log_entry_with_error(self):
"""Test LogEntry with error."""
entry = LogEntry(
client_key_id=1,
provider_id=2,
model="gpt-4o",
success=False,
error_message="Rate limit exceeded"
)
assert entry.success is False
assert entry.error_message == "Rate limit exceeded"
class TestAsyncLogger:
"""Test AsyncLogger."""
@pytest.mark.asyncio
async def test_start_creates_task(self):
"""Test start creates background task."""
logger = AsyncLogger()
assert logger._task is None
await logger.start()
assert logger._task is not None
await logger.stop()
@pytest.mark.asyncio
async def test_stop_cancels_task(self):
"""Test stop cancels background task."""
logger = AsyncLogger()
await logger.start()
assert logger._task is not None
await logger.stop()
assert logger._task is None
@pytest.mark.asyncio
async def test_log_queues_entry(self):
"""Test log queues an entry."""
logger = AsyncLogger()
entry = LogEntry(
client_key_id=1,
provider_id=2,
model="gpt-4o",
success=True
)
await logger.log(entry)
assert not logger._queue.empty()
@pytest.mark.asyncio
async def test_flush_batch_empty_queue(self, mock_db):
"""Test flush with empty queue."""
logger = AsyncLogger()
with patch("app.services.logger.db_connection", mock_db):
await logger._flush_batch() # Should not raise
@pytest.mark.asyncio
async def test_flush_batch_with_entries(self, mock_db):
"""Test flush with entries in queue."""
logger = AsyncLogger()
entry = LogEntry(
client_key_id=1,
provider_id=2,
model="gpt-4o",
prompt_tokens=10,
completion_tokens=20,
latency_ms=100,
success=True
)
await logger.log(entry)
with patch("app.services.logger.db_connection", mock_db):
await logger._flush_batch()
assert logger._queue.empty()
@pytest.mark.asyncio
async def test_flush_all(self, mock_db):
"""Test flush_all drains all entries."""
logger = AsyncLogger()
# Add multiple entries
for i in range(5):
entry = LogEntry(
client_key_id=1,
provider_id=2,
model=f"model-{i}",
success=True
)
await logger.log(entry)
with patch("app.services.logger.db_connection", mock_db):
await logger._flush_all()
assert logger._queue.empty()
@pytest.mark.asyncio
async def test_start_stop_cycle(self):
"""Test full start/stop cycle."""
logger = AsyncLogger()
await logger.start()
await asyncio.sleep(0.1) # Let task run briefly
await logger.stop()
assert logger._task is None
@pytest.mark.asyncio
async def test_multiple_start_calls_safe(self):
"""Test multiple start calls are safe."""
logger = AsyncLogger()
await logger.start()
first_task = logger._task
await logger.start() # Should not create new task
assert logger._task is first_task
await logger.stop()
@pytest.mark.asyncio
async def test_stop_without_start_safe(self):
"""Test stop without start is safe."""
logger = AsyncLogger()
await logger.stop() # Should not raise
def test_global_instance_exists(self):
"""Test global async_logger instance exists."""
assert async_logger is not None
assert isinstance(async_logger, AsyncLogger)