"""Integration tests for `mydeepagent pricing` CLI command.""" from __future__ import annotations import asyncio import tempfile from datetime import UTC, datetime from unittest.mock import patch from typer.testing import CliRunner from my_deepagent.cli.main import app from my_deepagent.persistence.db import Database from my_deepagent.persistence.models import ModelPricingRow runner = CliRunner() def _now_iso() -> str: return datetime.now(UTC).isoformat(timespec="seconds") async def _seed_pricing_rows(db: Database, rows: list[dict[str, object]]) -> None: from sqlalchemy.dialects.sqlite import insert as sqlite_insert async with db.session() as s: for r in rows: stmt = ( sqlite_insert(ModelPricingRow) .values(**r) .on_conflict_do_update( index_elements=["model"], set_={ "input_per_1k_usd": r["input_per_1k_usd"], "output_per_1k_usd": r["output_per_1k_usd"], "context_length": r["context_length"], "fetched_at": r["fetched_at"], }, ) ) await s.execute(stmt) # --------------------------------------------------------------------------- # Test 1: empty DB → "(no pricing data)" message # --------------------------------------------------------------------------- def test_pricing_empty_db_shows_no_data() -> None: with tempfile.TemporaryDirectory() as tmpdir: db_url = f"sqlite+aiosqlite:///{tmpdir}/test.sqlite3" with patch("my_deepagent.cli.stats.load_config") as mock_cfg: cfg = mock_cfg.return_value cfg.database_url = db_url result = runner.invoke(app, ["pricing"]) assert result.exit_code == 0, result.output assert "no pricing data" in result.output # --------------------------------------------------------------------------- # Test 2: with rows → table shown # --------------------------------------------------------------------------- def test_pricing_with_data_shows_table() -> None: with tempfile.TemporaryDirectory() as tmpdir: db_url = f"sqlite+aiosqlite:///{tmpdir}/test.sqlite3" db = Database(db_url) rows = [ { "model": "anthropic/claude-haiku-4-5", "input_per_1k_usd": 1.0, "output_per_1k_usd": 5.0, "context_length": 200_000, "fetched_at": _now_iso(), "raw_payload": "", }, { "model": "deepseek/deepseek-chat", "input_per_1k_usd": 0.28, "output_per_1k_usd": 1.12, "context_length": 64_000, "fetched_at": _now_iso(), "raw_payload": "", }, ] async def _init_and_seed() -> None: await db.init_schema() await _seed_pricing_rows(db, rows) await db.dispose() asyncio.run(_init_and_seed()) with patch("my_deepagent.cli.stats.load_config") as mock_cfg: cfg = mock_cfg.return_value cfg.database_url = db_url result = runner.invoke(app, ["pricing"]) assert result.exit_code == 0, result.output assert "anthropic/claude-haiku-4-5" in result.output assert "deepseek/deepseek-chat" in result.output assert "1.0000" in result.output assert "OpenRouter pricing" in result.output # --------------------------------------------------------------------------- # Test 3: models are sorted alphabetically # --------------------------------------------------------------------------- def test_pricing_rows_sorted_alphabetically() -> None: with tempfile.TemporaryDirectory() as tmpdir: db_url = f"sqlite+aiosqlite:///{tmpdir}/test.sqlite3" db = Database(db_url) rows = [ { "model": "zzz/last-model", "input_per_1k_usd": 9.0, "output_per_1k_usd": 9.0, "context_length": 1000, "fetched_at": _now_iso(), "raw_payload": "", }, { "model": "aaa/first-model", "input_per_1k_usd": 1.0, "output_per_1k_usd": 1.0, "context_length": 2000, "fetched_at": _now_iso(), "raw_payload": "", }, ] async def _init_and_seed() -> None: await db.init_schema() await _seed_pricing_rows(db, rows) await db.dispose() asyncio.run(_init_and_seed()) with patch("my_deepagent.cli.stats.load_config") as mock_cfg: cfg = mock_cfg.return_value cfg.database_url = db_url result = runner.invoke(app, ["pricing"]) assert result.exit_code == 0, result.output pos_first = result.output.find("aaa/first-model") pos_last = result.output.find("zzz/last-model") assert pos_first != -1 assert pos_last != -1 assert pos_first < pos_last, "aaa/first-model should appear before zzz/last-model"