"""Unit tests for PostgresModelUsageStore, using a mocked psycopg2 pool. Mirrors the mocking pattern in backend/tests/perception/test_postgres_event_store.py — no real database is needed. """ from __future__ import annotations import sys from datetime import datetime, timezone from unittest.mock import MagicMock, patch # Patch psycopg2 before importing the module under test. mock_psycopg2 = MagicMock() mock_psycopg2.extras = MagicMock() sys.modules.setdefault("psycopg2", mock_psycopg2) sys.modules.setdefault("psycopg2.extras", mock_psycopg2.extras) sys.modules.setdefault("psycopg2.pool", MagicMock()) from app.shared.model_usage_tracker import ModelUsageEntry def _cursor_returning(rows): """Build a MagicMock standing in for a psycopg2 cursor context manager.""" cursor = MagicMock() cursor.__enter__ = lambda s: s cursor.__exit__ = MagicMock(return_value=False) cursor.fetchall.return_value = rows return cursor @patch("app.infrastructure.storage.postgres_model_usage_store.PostgresModelUsageStore._ensure_schema") @patch("app.infrastructure.storage.postgres_model_usage_store.ThreadedConnectionPool") def test_load_all_returns_entries_keyed_by_provider_model(mock_pool_class, mock_ensure): """load_all() must turn each row into a ModelUsageEntry keyed by 'provider:model'.""" row = { "provider": "deepseek", "model": "deepseek-v4-flash", "total_tokens": 100, "prompt_tokens": 60, "completion_tokens": 40, "call_count_ok": 5, "call_count_error": 1, "last_called_at": datetime(2026, 7, 23, tzinfo=timezone.utc), "last_latency_ms": 250, "last_error": None, } mock_pool = MagicMock() mock_pool_class.return_value = mock_pool conn = MagicMock() conn.__enter__ = lambda s: s conn.__exit__ = MagicMock(return_value=False) conn.cursor.return_value = _cursor_returning([row]) mock_pool.getconn.return_value = conn from app.infrastructure.storage.postgres_model_usage_store import PostgresModelUsageStore store = PostgresModelUsageStore() entries = store.load_all() assert "deepseek:deepseek-v4-flash" in entries entry = entries["deepseek:deepseek-v4-flash"] assert isinstance(entry, ModelUsageEntry) assert entry.total_tokens == 100 assert entry.call_count_error == 1 @patch("app.infrastructure.storage.postgres_model_usage_store.PostgresModelUsageStore._ensure_schema") @patch("app.infrastructure.storage.postgres_model_usage_store.ThreadedConnectionPool") def test_flush_upserts_every_entry(mock_pool_class, mock_ensure): """flush() must execute one UPSERT per tracked entry and commit once.""" mock_pool = MagicMock() mock_pool_class.return_value = mock_pool conn = MagicMock() conn.__enter__ = lambda s: s conn.__exit__ = MagicMock(return_value=False) cursor = MagicMock() cursor.__enter__ = lambda s: s cursor.__exit__ = MagicMock(return_value=False) conn.cursor.return_value = cursor mock_pool.getconn.return_value = conn from app.infrastructure.storage.postgres_model_usage_store import PostgresModelUsageStore store = PostgresModelUsageStore() entries = { "deepseek:deepseek-v4-flash": ModelUsageEntry( provider="deepseek", model="deepseek-v4-flash", total_tokens=100, call_count_ok=5, ), } store.flush(entries) assert cursor.execute.call_count == 1 conn.commit.assert_called_once() @patch("app.infrastructure.storage.postgres_model_usage_store.PostgresModelUsageStore._ensure_schema") @patch("app.infrastructure.storage.postgres_model_usage_store.ThreadedConnectionPool") def test_flush_with_no_entries_does_not_touch_the_database(mock_pool_class, mock_ensure): """flush({}) must be a no-op — no point opening a connection for nothing.""" mock_pool = MagicMock() mock_pool_class.return_value = mock_pool from app.infrastructure.storage.postgres_model_usage_store import PostgresModelUsageStore store = PostgresModelUsageStore() store.flush({}) mock_pool.getconn.assert_not_called()