136 lines
5.2 KiB
Python
136 lines
5.2 KiB
Python
# #region Test.PreviewTokenEstimator [C:2] [TYPE Module] [SEMANTICS test, translate, token, cost, estimation]
|
|
# @BRIEF Tests for preview_token_estimator.py — TokenEstimator static methods.
|
|
# @RELATION BINDS_TO -> [TokenEstimator]
|
|
# @TEST_CONTRACT: TokenEstimator.estimate_prompt_tokens -> int | token estimate from string
|
|
# @TEST_CONTRACT: TokenEstimator.estimate_output_tokens -> int | output token estimate
|
|
# @TEST_CONTRACT: TokenEstimator.estimate_cost -> float | cost estimate from tokens
|
|
# @TEST_CONTRACT: TokenEstimator.check_cost_warning -> str | None | cost threshold warning
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
|
|
|
|
import os
|
|
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-testing")
|
|
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
|
|
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")
|
|
|
|
import pytest
|
|
|
|
from src.plugins.translate.preview_token_estimator import TokenEstimator
|
|
|
|
|
|
class TestEstimatePromptTokens:
|
|
"""TokenEstimator.estimate_prompt_tokens — estimate tokens from string."""
|
|
|
|
def test_empty_string_returns_zero(self):
|
|
"""Empty string returns 0."""
|
|
assert TokenEstimator.estimate_prompt_tokens("") == 0
|
|
|
|
def test_short_string_returns_at_least_one(self):
|
|
"""Short string returns at least 1."""
|
|
assert TokenEstimator.estimate_prompt_tokens("a") == 1
|
|
|
|
def test_long_string_estimates(self):
|
|
"""Longer string produces token estimate."""
|
|
text = "hello world " * 100 # ~1300 chars
|
|
tokens = TokenEstimator.estimate_prompt_tokens(text)
|
|
assert tokens > 0
|
|
assert tokens <= len(text) # tokens shouldn't exceed chars
|
|
|
|
def test_empty_string_handled(self):
|
|
"""Empty string returns 0. Whitespace is truthy so gets min 1."""
|
|
assert TokenEstimator.estimate_prompt_tokens("") == 0
|
|
assert TokenEstimator.estimate_prompt_tokens(" ") == 1
|
|
|
|
|
|
class TestEstimateOutputTokens:
|
|
"""TokenEstimator.estimate_output_tokens — estimate output tokens."""
|
|
|
|
def test_zero_rows_returns_zero(self):
|
|
"""0 rows returns 0."""
|
|
assert TokenEstimator.estimate_output_tokens(0) == 0
|
|
|
|
def test_single_row_single_language(self):
|
|
"""1 row, 1 language gives output estimate."""
|
|
result = TokenEstimator.estimate_output_tokens(1, 1)
|
|
expected = int(1 * 1 * 120 * 1.2)
|
|
assert result == expected
|
|
|
|
def test_multi_row_single_language(self):
|
|
"""10 rows, 1 language."""
|
|
result = TokenEstimator.estimate_output_tokens(10, 1)
|
|
expected = int(10 * 1 * 120 * 1.2)
|
|
assert result == expected
|
|
|
|
def test_multi_row_multi_language(self):
|
|
"""5 rows, 3 languages."""
|
|
result = TokenEstimator.estimate_output_tokens(5, 3)
|
|
expected = int(5 * 3 * 120 * 1.2)
|
|
assert result == expected
|
|
|
|
def test_default_language(self):
|
|
"""Default num_languages is 1."""
|
|
result = TokenEstimator.estimate_output_tokens(2)
|
|
expected = int(2 * 1 * 120 * 1.2)
|
|
assert result == expected
|
|
|
|
|
|
class TestEstimateCost:
|
|
"""TokenEstimator.estimate_cost — cost from tokens."""
|
|
|
|
def test_zero_tokens_zero_cost(self):
|
|
"""0 tokens costs 0."""
|
|
assert TokenEstimator.estimate_cost(0) == 0.0
|
|
|
|
def test_default_rate(self):
|
|
"""1000 tokens at default rate."""
|
|
cost = TokenEstimator.estimate_cost(1000)
|
|
assert cost == pytest.approx(0.002, 0.000001)
|
|
|
|
def test_custom_rate(self):
|
|
"""Custom cost per 1k tokens."""
|
|
cost = TokenEstimator.estimate_cost(1000, 0.01)
|
|
assert cost == pytest.approx(0.01, 0.000001)
|
|
|
|
def test_large_token_count(self):
|
|
"""Large token count calculates correctly."""
|
|
cost = TokenEstimator.estimate_cost(100000)
|
|
assert cost == pytest.approx(0.2, 0.000001)
|
|
|
|
def test_rounding_precision(self):
|
|
"""Result is rounded to 6 decimal places."""
|
|
cost = TokenEstimator.estimate_cost(333)
|
|
assert isinstance(cost, float)
|
|
|
|
|
|
class TestCheckCostWarning:
|
|
"""TokenEstimator.check_cost_warning — cost threshold warning."""
|
|
|
|
def test_below_threshold_no_warning(self):
|
|
"""sample_size <= 30 returns None."""
|
|
assert TokenEstimator.check_cost_warning(10, 1, 100, 0.001) is None
|
|
assert TokenEstimator.check_cost_warning(30, 1, 100, 0.001) is None
|
|
|
|
def test_above_threshold_returns_warning(self):
|
|
"""sample_size > 30 returns warning string."""
|
|
warning = TokenEstimator.check_cost_warning(50, 2, 5000, 0.05)
|
|
assert warning is not None
|
|
assert "Large preview" in warning
|
|
assert "5000 tokens" in warning
|
|
assert "$0.0500" in warning
|
|
assert "2 languages" in warning
|
|
|
|
def test_warning_format_high_cost(self):
|
|
"""Warning includes formatted cost."""
|
|
warning = TokenEstimator.check_cost_warning(100, 1, 10000, 0.5)
|
|
assert warning is not None
|
|
assert "$0.5000" in warning
|
|
|
|
def test_single_language_format(self):
|
|
"""Warning for single language."""
|
|
warning = TokenEstimator.check_cost_warning(40, 1, 2000, 0.004)
|
|
assert warning is not None
|
|
assert "1 languages" in warning
|
|
# #endregion Test.PreviewTokenEstimator
|