"""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)) def test_estimate_equals_hand_computed() -> None: """Anchor the integer-øre math: m-cheap = 10 øre/1k, effort standard (100%), max_tokens 100_000 → 100_000*100//100 * 10 // 1000 = 1000 øre/run; 3 projects → 3000 øre.""" pricing = costsim.PricingContract(**_VALID_PRICING) assert costsim.estimate_run_ore("m-cheap", "standard", pricing, max_tokens=100_000) == 1000 assert ( costsim.estimate_portfolio_ore( "m-cheap", "standard", pricing, n_projects=3, max_tokens=100_000 ) == 3000 ) def test_unknown_effort_raises() -> None: pricing = costsim.PricingContract(**_VALID_PRICING) with pytest.raises(ValueError, match="unknown effort"): costsim.estimate_run_ore("m-cheap", "turbo", pricing) def test_estimate_table_structure() -> None: pricing = costsim.PricingContract(**_VALID_PRICING) mm = {"prof": {"proposer": "m-cheap", "checker": "m-dear"}} table = costsim.build_estimate_table( "prof", ["low", "high"], pricing, n_projects=2, model_map=mm ) assert len(table["rows"]) == 4 # 2 roles × 2 efforts for row in table["rows"]: assert row["estimat_type"] == "øvre grense" assert isinstance(row["estimat_kr"], str) # edge-formatted string, never a float assert table["kost_mot_verdi"]["kjoring_kostet_ore"] > 0 assert table["pricing_source"] == "unit-test prices" def test_ore_to_kr_str_formats_money() -> None: """Value-level guard for the single money-display edge (``_ore_to_kr_str``): the integer-øre total → user-facing NOK string. Pins the actual Norwegian formatting — non-breaking-space (\\xa0) thousands separator + comma decimals — so a regression (wrong ``divmod``, dropped padding, øre/kr transposition, or the \\xa0 → ASCII space) fails loudly instead of shipping a wrong figure. A structural ``isinstance(..., str)`` check cannot catch any of those.""" # thousands grouping (docstring's own worked example) assert costsim._ore_to_kr_str(123456) == "1\xa0234,56\xa0kr" # sub-100-øre → two-digit zero-padded rest assert costsim._ore_to_kr_str(5) == "0,05\xa0kr" # negative-sign branch assert costsim._ore_to_kr_str(-123456) == "-1\xa0234,56\xa0kr" def test_cli_prints_estimate_table(capsys: pytest.CaptureFixture[str]) -> None: """CLI smoke: ``main`` prints the estimate table + kost-mot-verdi + adoption footer, rc 0.""" rc = costsim.main(["--projects", "4", "--profile", "local"]) out = capsys.readouterr().out assert rc == 0 assert "Kostnadsestimat" in out assert "kost-mot-verdi" in out assert "Adopsjonssti" in out def test_cli_bad_config_returns_nonzero(capsys: pytest.CaptureFixture[str]) -> None: rc = costsim.main(["--pricing", "/nonexistent/pricing.json"]) assert rc == 1