headroom/tests/test_providers/test_deepseek.py
JD Davis 1bea0ea31a
test: track active LiteLLM DeepSeek pricing (#3161)
## Description

Keep the LiteLLM DeepSeek V4 integration tests compatible with
upstream-owned pricing entries. LiteLLM now publishes these models
directly, so Headroom correctly preserves upstream values instead of
installing its fallback values; the tests must validate the active entry
rather than require fallback prices.

Related: #3157

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Validate that active upstream DeepSeek V4 price entries contain
positive input and output prices.
- Compare `cost_per_token` results with the active LiteLLM model-cost
entry.
- Preserve the existing fallback-price and non-overwrite coverage.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [ ] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
python -m pytest tests/test_providers/test_deepseek.py -q
20 passed in 4.63s

ruff check tests/test_providers/test_deepseek.py
All checks passed!

ruff format --check tests/test_providers/test_deepseek.py
1 file already formatted

pre-commit: Ruff alignment, merge-conflict check, Ruff, Ruff format, and mypy all passed
```

## Real Behavior Proof

- Environment: Windows, Python 3.13.13, LiteLLM model-cost data
available.
- Exact command / steps: `python -m pytest
tests/test_providers/test_deepseek.py -q`
- Observed result: all 20 DeepSeek provider and pricing tests pass
against the active LiteLLM entries.
- Not tested: provider API calls; this change only concerns local
pricing metadata assertions.

## Runtime Rollout Safety

- Rollout-managed feature(s): None.
- Minimum rollout channel: N/A.
- Stable/default behavior changed: No runtime behavior changes.
- Kill switch / disable path: N/A.
- Unsafe override required: No.
- Qualification impact: Restores deterministic CI coverage for
upstream-owned pricing entries.
- Rollback path: Revert this test-only commit.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code where needed
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] Existing tests prove the fix is effective
- [x] New and existing unit tests pass locally with my changes
- [x] I did **not** edit `CHANGELOG.md` — it is generated by
release-please from my Conventional Commit PR title (a CI guard enforces
this)

## Screenshots (if applicable)

N/A — test-only change.

## Additional Notes

Documentation changes are not applicable because runtime behavior and
public APIs are unchanged.
2026-08-20 23:19:55 -05:00

233 lines
9.2 KiB
Python

"""Tests for DeepSeek model pricing and cost estimation."""
import pytest
from headroom.pricing.deepseek_prices import (
DEEPSEEK_PRICES,
get_deepseek_registry,
)
from headroom.pricing.registry import PricingRegistry
class TestDeepSeekPricingModule:
"""Tests for the DeepSeek pricing data module."""
def test_deepseek_pricing_contains_v4_models(self):
assert "deepseek-v4-flash" in DEEPSEEK_PRICES
assert "deepseek-v4-pro" in DEEPSEEK_PRICES
assert len(DEEPSEEK_PRICES) == 2
def test_deepseek_v4_flash_pricing(self):
pricing = DEEPSEEK_PRICES["deepseek-v4-flash"]
assert pricing.input_per_1m == 0.14
assert pricing.output_per_1m == 0.28
assert pricing.cached_input_per_1m == 0.0028
assert pricing.context_window == 1_000_000
assert pricing.provider == "deepseek"
assert pricing.notes is not None
def test_deepseek_v4_pro_pricing(self):
pricing = DEEPSEEK_PRICES["deepseek-v4-pro"]
assert pricing.input_per_1m == 0.435
assert pricing.output_per_1m == 0.87
assert pricing.cached_input_per_1m == 0.003625
assert pricing.context_window == 1_000_000
assert pricing.provider == "deepseek"
assert pricing.notes is not None
def test_get_deepseek_registry(self):
registry = get_deepseek_registry()
assert isinstance(registry, PricingRegistry)
assert registry.get_price("deepseek-v4-flash") is not None
assert registry.get_price("deepseek-v4-pro") is not None
assert registry.get_price("nonexistent") is None
def test_registry_source_url(self):
registry = get_deepseek_registry()
assert registry.source_url == "https://api-docs.deepseek.com/quick_start/pricing"
# Deliberately no `assert not registry.is_stale()` here: is_stale()
# compares the shipped LAST_UPDATED against date.today(), so asserting
# freshness makes this test fail on wall-clock time alone once the
# pricing date ages past STALENESS_THRESHOLD_DAYS (30) - which then
# breaks CI on every unrelated PR in the repo. The staleness mechanism
# is covered time-independently in tests/test_pricing.py, and the
# sibling Anthropic/OpenAI registries make no freshness assertion.
def test_deepseek_registry_estimate_cost(self):
registry = get_deepseek_registry()
cost = registry.estimate_cost("deepseek-v4-flash", input_tokens=1_000_000)
assert cost.cost_usd == 0.14
assert "input" in cost.breakdown
assert cost.pricing_date is not None
def test_deepseek_registry_estimate_cost_with_cached(self):
registry = get_deepseek_registry()
cost = registry.estimate_cost(
"deepseek-v4-flash",
input_tokens=1_000_000,
cached_input_tokens=1_000_000,
)
assert cost.cost_usd == 0.14 + 0.0028
class TestDeepSeekLiteLLMInjection:
"""Tests for DeepSeek V4 pricing injection into litellm."""
def test_deepseek_v4_models_in_litellm_model_cost(self):
from headroom.pricing.litellm_pricing import LITELLM_AVAILABLE, litellm
if not LITELLM_AVAILABLE:
pytest.skip("litellm not available")
assert "deepseek-v4-flash" in litellm.model_cost
assert "deepseek-v4-pro" in litellm.model_cost
def test_deepseek_v4_prefixed_models_in_litellm_model_cost(self):
from headroom.pricing.litellm_pricing import LITELLM_AVAILABLE, litellm
if not LITELLM_AVAILABLE:
pytest.skip("litellm not available")
assert "deepseek/deepseek-v4-flash" in litellm.model_cost
assert "deepseek/deepseek-v4-pro" in litellm.model_cost
def test_deepseek_v4_flash_litellm_pricing(self):
from headroom.pricing.litellm_pricing import LITELLM_AVAILABLE, litellm
if not LITELLM_AVAILABLE:
pytest.skip("litellm not available")
flash = litellm.model_cost["deepseek-v4-flash"]
assert flash["input_cost_per_token"] > 0
assert flash["output_cost_per_token"] > 0
assert flash["litellm_provider"] == "deepseek"
def test_deepseek_v4_pro_litellm_pricing(self):
from headroom.pricing.litellm_pricing import LITELLM_AVAILABLE, litellm
if not LITELLM_AVAILABLE:
pytest.skip("litellm not available")
pro = litellm.model_cost["deepseek-v4-pro"]
assert pro["input_cost_per_token"] > 0
assert pro["output_cost_per_token"] > 0
assert pro["litellm_provider"] == "deepseek"
def test_cost_per_token_resolves_deepseek_v4_flash(self):
from headroom.pricing.litellm_pricing import (
LITELLM_AVAILABLE,
litellm,
resolve_litellm_model,
)
if not LITELLM_AVAILABLE:
pytest.skip("litellm not available")
# resolve_litellm_model adds the deepseek/ prefix so that
# litellm.cost_per_token can determine the provider via
# get_llm_provider(). Bare model names without a provider prefix
# would fail with BadRequestError.
resolved = resolve_litellm_model("deepseek-v4-flash")
input_cost, output_cost = litellm.cost_per_token(
model=resolved,
prompt_tokens=1_000_000,
completion_tokens=1_000_000,
)
active_pricing = litellm.model_cost["deepseek-v4-flash"]
assert input_cost == pytest.approx(
active_pricing["input_cost_per_token"] * 1_000_000,
)
assert output_cost == pytest.approx(
active_pricing["output_cost_per_token"] * 1_000_000,
)
def test_resolve_litellm_model_prefixes_deepseek(self):
from headroom.pricing.litellm_pricing import resolve_litellm_model
resolved = resolve_litellm_model("deepseek-v4-flash")
assert resolved == "deepseek/deepseek-v4-flash"
def test_injection_does_not_overwrite_existing_upstream_entries(self):
"""If litellm upstream already has these, our injection is a no-op."""
from headroom.pricing.litellm_pricing import LITELLM_AVAILABLE, litellm
if not LITELLM_AVAILABLE:
pytest.skip("litellm not available")
# Force-inject with wrong value, then verify the injection guard
litellm.model_cost["deepseek-v4-flash"] = {"input_cost_per_token": 999}
# Reimport to trigger _inject_deepseek_pricing — but it should NOT overwrite
import importlib
import headroom.pricing.litellm_pricing as lp
importlib.reload(lp)
assert litellm.model_cost["deepseek-v4-flash"]["input_cost_per_token"] == 999
# Reset to correct value
litellm.model_cost["deepseek-v4-flash"] = {
"input_cost_per_token": 0.14 / 1_000_000,
"output_cost_per_token": 0.28 / 1_000_000,
"cache_read_input_token_cost": 0.0028 / 1_000_000,
"litellm_provider": "deepseek",
"max_tokens": 384_000,
"max_input_tokens": 1_000_000,
}
class TestDeepSeekAnthropicProviderFallback:
"""Tests that Anthropic provider's _get_pricing handles DeepSeek models."""
def test_deepseek_v4_flash_fallback(self):
from headroom.providers.anthropic import AnthropicProvider
provider = AnthropicProvider()
pricing = provider._get_pricing("deepseek-v4-flash")
assert pricing is not None
assert pricing["input"] == 0.14
assert pricing["output"] == 0.28
assert pricing["cached_input"] == 0.0028
def test_deepseek_v4_pro_fallback(self):
from headroom.providers.anthropic import AnthropicProvider
provider = AnthropicProvider()
pricing = provider._get_pricing("deepseek-v4-pro")
assert pricing is not None
assert pricing["input"] == 0.435
assert pricing["output"] == 0.87
assert pricing["cached_input"] == 0.003625
def test_deepseek_unknown_model_returns_none(self):
from headroom.providers.anthropic import AnthropicProvider
provider = AnthropicProvider()
pricing = provider._get_pricing("deepseek-unknown-model")
assert pricing is None
def test_deepseek_partial_match_v4_flash_alias(self):
from headroom.providers.anthropic import AnthropicProvider
provider = AnthropicProvider()
# Should match via partial match (flash in v4-flash)
pricing = provider._get_pricing("deepseek-v4-flash-v1")
assert pricing is not None
def test_estimate_cost_deepseek_v4_flash(self):
from headroom.providers.anthropic import AnthropicProvider
provider = AnthropicProvider()
cost = provider.estimate_cost(
input_tokens=1_000_000,
output_tokens=0,
model="deepseek-v4-flash",
)
assert cost is not None
assert cost == 0.14
def test_estimate_cost_deepseek_v4_flash_with_cache(self):
from headroom.providers.anthropic import AnthropicProvider
provider = AnthropicProvider()
cost = provider.estimate_cost(
input_tokens=1_000_000,
output_tokens=0,
model="deepseek-v4-flash",
cached_tokens=1_000_000,
)
assert cost is not None
# All 1M input tokens are cached, so cost = cached_input only
assert cost == 0.0028