"""S3.6 costsim — unit tests: pricing load/fail-fast, deterministic estimate, table, CLI. Load-bearing detach seams live in ``test_costsim_loadbearing.py``; these are the happy-path + schema unit tests. """ from __future__ import annotations import json from pathlib import Path import pytest from pydantic import ValidationError from portfolio_optimiser import costsim _VALID_PRICING = { "source": "unit-test prices", "date": "2026-07-15", "models": { "m-cheap": { "ore_per_1k_tokens": 10, "quality_guidance": {"note": "cheap draft model", "source": "test-src 2026-07-15"}, }, "m-dear": { "ore_per_1k_tokens": 50, "quality_guidance": {"note": "stronger reasoning", "source": "test-src 2026-07-15"}, }, }, } def _write(tmp_path: Path, data: dict) -> Path: p = tmp_path / "pricing.json" p.write_text(json.dumps(data), encoding="utf-8") return p def test_load_pricing_valid(tmp_path: Path) -> None: pricing = costsim.load_pricing(_write(tmp_path, _VALID_PRICING)) assert pricing.models["m-cheap"].ore_per_1k_tokens == 10 assert pricing.source == "unit-test prices" def test_load_pricing_bundled_default() -> None: """No path → the packaged data/pricing.example.json loads and prices the local model.""" pricing = costsim.load_pricing() assert "qwen3:4b" in pricing.models def test_load_pricing_missing_file_raises(tmp_path: Path) -> None: with pytest.raises(FileNotFoundError): costsim.load_pricing(tmp_path / "nope.json") def test_load_pricing_missing_provenance_raises(tmp_path: Path) -> None: bad = {"models": _VALID_PRICING["models"]} # no source/date with pytest.raises(ValidationError): costsim.load_pricing(_write(tmp_path, bad))