"""Tests para el calculo de costes del orquestador. Replica la formula de coste de OrchestratorEngine._run_pipeline(): cost_usd = (input_tokens / 1_000_000) * cost_per_1m_input + (output_tokens / 1_000_000) * cost_per_1m_output Defaults: cost_per_1m_input=2.50, cost_per_1m_output=15.00 """ import pytest def calculate_cost( input_tokens: int, output_tokens: int, cost_per_1m_input: float = 2.50, cost_per_1m_output: float = 15.00, ) -> float: """Replica exacta de la formula de coste en engine.py.""" return ( (input_tokens / 1_000_000) * cost_per_1m_input + (output_tokens / 1_000_000) * cost_per_1m_output ) class TestCostCalculation: def test_1m_input_tokens(self): cost = calculate_cost(1_000_000, 0) assert cost == pytest.approx(2.50) def test_1m_output_tokens(self): cost = calculate_cost(0, 1_000_000) assert cost == pytest.approx(15.00) def test_500k_input_100k_output(self): cost = calculate_cost(500_000, 100_000) # (500_000 / 1_000_000) * 2.50 + (100_000 / 1_000_000) * 15.00 # = 1.25 + 1.50 = 2.75 assert cost == pytest.approx(2.75) def test_zero_tokens(self): cost = calculate_cost(0, 0) assert cost == 0.0 def test_custom_pricing(self): cost = calculate_cost( 1_000_000, 1_000_000, cost_per_1m_input=3.00, cost_per_1m_output=10.00, ) assert cost == pytest.approx(13.00) def test_small_token_count(self): """Pocos tokens = coste muy bajo pero no cero.""" cost = calculate_cost(100, 50) assert cost > 0 assert cost < 0.01 def test_round_to_6_decimals(self): """El engine hace round(cost_usd, 6).""" cost = calculate_cost(1, 1) rounded = round(cost, 6) # (1/1M)*2.50 + (1/1M)*15.00 = 1.75e-05 # round(1.75e-05, 6) = 1.7e-05 (banker's rounding: 5 rounds to even) assert rounded == pytest.approx(0.000017, abs=1e-7) def test_output_more_expensive_than_input(self): """Con defaults, output es 6x mas caro que input.""" input_cost = calculate_cost(1_000_000, 0) output_cost = calculate_cost(0, 1_000_000) assert output_cost == pytest.approx(input_cost * 6.0)