Merge v0.1.4 changes from 'origin/main' into v0.2.0-final

This commit is contained in:
Shroominic
2025-10-27 14:07:37 +08:00
7 changed files with 825 additions and 13 deletions
+2
View File
@@ -12,6 +12,8 @@ dist/
# Development
.notes
.*keys.db
*.db-shm
*.db-wal
.*wallet.sqlite3
*models.json
.cashu
+2
View File
@@ -105,6 +105,8 @@ async def balances_for_mint_and_unit(
async def init_db() -> None:
"""Initializes the database and creates tables if they don't exist."""
async with engine.begin() as conn:
if DATABASE_URL.startswith("sqlite"):
await conn.exec_driver_sql("PRAGMA journal_mode=WAL")
await conn.run_sync(SQLModel.metadata.create_all)
+5 -1
View File
@@ -1,4 +1,5 @@
import asyncio
import os
from contextlib import asynccontextmanager
from pathlib import Path
from typing import AsyncGenerator
@@ -31,7 +32,10 @@ from .settings import settings as global_settings
setup_logging()
logger = get_logger(__name__)
__version__ = "0.2.0-dev"
if os.getenv("VERSION_SUFFIX") is not None:
__version__ = f"0.2.0-{os.getenv('VERSION_SUFFIX')}"
else:
__version__ = "0.2.0-dev"
@asynccontextmanager
+2 -2
View File
@@ -64,7 +64,7 @@ class Settings(BaseSettings):
default=120, env="PRICING_REFRESH_INTERVAL_SECONDS"
)
models_refresh_interval_seconds: int = Field(
default=0, env="MODELS_REFRESH_INTERVAL_SECONDS"
default=360, env="MODELS_REFRESH_INTERVAL_SECONDS"
)
enable_pricing_refresh: bool = Field(default=True, env="ENABLE_PRICING_REFRESH")
enable_models_refresh: bool = Field(default=True, env="ENABLE_MODELS_REFRESH")
@@ -234,7 +234,7 @@ class SettingsService:
merged_dict: dict[str, Any] = dict(env_resolved.dict())
merged_dict.update(
{k: v for k, v in db_json.items() if v not in (None, "")}
{k: v for k, v in db_json.items() if v not in (None, "", []) and v}
)
# Ensure primary_mint is consistent with cashu_mints if not explicitly set
+7
View File
@@ -413,6 +413,13 @@ def parse_request_body_json(request_body: bytes, path: str) -> dict[str, Any]:
if request_body:
try:
request_body_dict = json.loads(request_body)
if "max_tokens" in request_body_dict:
raise HTTPException(
status_code=400,
detail={"error": "max_tokens must be an integer (without quotes)"},
)
logger.debug(
"Request body parsed",
extra={
+26 -10
View File
@@ -140,7 +140,9 @@ class UpstreamProvider:
"""
return model_id
def prepare_request_body(self, body: bytes | None, model_obj: Model) -> bytes | None:
def prepare_request_body(
self, body: bytes | None, model_obj: Model
) -> bytes | None:
"""Transform request body for provider-specific requirements.
Automatically transforms model names in the request body.
@@ -1019,6 +1021,7 @@ class UpstreamProvider:
amount: int,
unit: str,
max_cost_for_model: int,
mint: str | None = None,
) -> StreamingResponse:
"""Handle streaming response for X-Cashu payment, calculating refund if needed.
@@ -1099,7 +1102,7 @@ class UpstreamProvider:
},
)
refund_token = await self.send_refund(refund_amount, unit)
refund_token = await self.send_refund(refund_amount, unit, mint)
response_headers["X-Cashu"] = refund_token
logger.info(
@@ -1151,6 +1154,7 @@ class UpstreamProvider:
amount: int,
unit: str,
max_cost_for_model: int,
mint: str | None = None,
) -> Response:
"""Handle non-streaming response for X-Cashu payment, calculating refund if needed.
@@ -1221,7 +1225,7 @@ class UpstreamProvider:
)
if refund_amount > 0:
refund_token = await self.send_refund(refund_amount, unit)
refund_token = await self.send_refund(refund_amount, unit, mint)
response_headers["X-Cashu"] = refund_token
logger.info(
@@ -1255,7 +1259,7 @@ class UpstreamProvider:
)
emergency_refund = amount
refund_token = await send_token(emergency_refund, unit=unit)
refund_token = await send_token(emergency_refund, unit=unit, mint_url=mint)
response.headers["X-Cashu"] = refund_token
logger.warning(
@@ -1275,7 +1279,12 @@ class UpstreamProvider:
)
async def handle_x_cashu_chat_completion(
self, response: httpx.Response, amount: int, unit: str, max_cost_for_model: int
self,
response: httpx.Response,
amount: int,
unit: str,
max_cost_for_model: int,
mint: str | None = None,
) -> StreamingResponse | Response:
"""Handle chat completion response for X-Cashu payment, detecting streaming vs non-streaming.
@@ -1312,11 +1321,11 @@ class UpstreamProvider:
if is_streaming:
return await self.handle_x_cashu_streaming_response(
content_str, response, amount, unit, max_cost_for_model
content_str, response, amount, unit, max_cost_for_model, mint
)
else:
return await self.handle_x_cashu_non_streaming_response(
content_str, response, amount, unit, max_cost_for_model
content_str, response, amount, unit, max_cost_for_model, mint
)
except Exception as e:
@@ -1344,6 +1353,7 @@ class UpstreamProvider:
unit: str,
max_cost_for_model: int,
model_obj: Model,
mint: str | None = None,
) -> Response | StreamingResponse:
"""Forward request paid with X-Cashu token to upstream service.
@@ -1414,7 +1424,7 @@ class UpstreamProvider:
},
)
refund_token = await self.send_refund(amount - 60, unit)
refund_token = await self.send_refund(amount - 60, unit, mint)
logger.info(
"Refund processed for failed upstream request",
@@ -1452,7 +1462,7 @@ class UpstreamProvider:
)
result = await self.handle_x_cashu_chat_completion(
response, amount, unit, max_cost_for_model
response, amount, unit, max_cost_for_model, mint
)
background_tasks = BackgroundTasks()
background_tasks.add_task(response.aclose)
@@ -1496,7 +1506,12 @@ class UpstreamProvider:
)
async def handle_x_cashu(
self, request: Request, x_cashu_token: str, path: str, max_cost_for_model: int, model_obj: Model
self,
request: Request,
x_cashu_token: str,
path: str,
max_cost_for_model: int,
model_obj: Model,
) -> Response | StreamingResponse:
"""Handle request with X-Cashu token payment, redeeming token and forwarding request.
@@ -1539,6 +1554,7 @@ class UpstreamProvider:
unit,
max_cost_for_model,
model_obj,
mint,
)
except Exception as e:
error_message = str(e)
+781
View File
@@ -0,0 +1,781 @@
"""Unit tests for upstream provider fee application to USD pricing.
This module tests the fix for issue #188: "Upstream provider fee not being applied
to USD pricing (only sats)". The fix ensures that both exchange_fee and
upstream_provider_fee are correctly applied to USD pricing when models are stored
in the database via the _model_to_row_payload function.
Key behaviors tested:
1. exchange_fee is applied to all USD pricing fields
2. upstream_provider_fee is applied to all USD pricing fields
3. Both fees are compounded correctly (multiplied together)
4. Fees apply to all pricing attributes (prompt, completion, request, etc.)
5. Fees apply to max cost fields (max_prompt_cost, max_completion_cost, max_cost)
6. sats_pricing remains unaffected (fees not double-applied)
7. Zero-value pricing fields are handled correctly
8. Edge cases and boundary conditions are properly handled
"""
import json
import os
from unittest.mock import patch
import pytest
# Set required env vars before importing
os.environ["UPSTREAM_BASE_URL"] = "http://test"
os.environ["UPSTREAM_API_KEY"] = "test"
from routstr.core.settings import settings # noqa: E402
from routstr.payment.models import ( # noqa: E402
Architecture,
Model,
Pricing,
_model_to_row_payload,
)
@pytest.fixture
def base_architecture() -> Architecture:
"""Provide standard architecture for test models."""
return Architecture(
modality="text",
input_modalities=["text"],
output_modalities=["text"],
tokenizer="gpt",
instruct_type="chat",
)
@pytest.fixture
def standard_pricing() -> Pricing:
"""Provide standard USD pricing with known values for testing."""
return Pricing(
prompt=0.001, # $0.001 per prompt token
completion=0.002, # $0.002 per completion token
request=0.01, # $0.01 per request
image=0.05, # $0.05 per image
web_search=0.03, # $0.03 per web search
internal_reasoning=0.015, # $0.015 per internal reasoning token
max_prompt_cost=10.0, # $10 max prompt cost
max_completion_cost=20.0, # $20 max completion cost
max_cost=30.0, # $30 max total cost
)
@pytest.fixture
def standard_model(base_architecture: Architecture, standard_pricing: Pricing) -> Model:
"""Create a standard test model with known pricing."""
return Model(
id="test-model-standard",
name="Test Model Standard",
created=1234567890,
description="A standard test model for fee application",
context_length=8192,
architecture=base_architecture,
pricing=standard_pricing,
)
# =============================================================================
# Individual Fee Application Tests
# =============================================================================
def test_exchange_fee_applied_to_usd_pricing(standard_model: Model) -> None:
"""Verify exchange_fee is applied to all USD pricing fields."""
exchange_fee = 1.005 # 0.5% fee
upstream_fee = 1.0 # No upstream fee
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(standard_model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# Verify all pricing fields have exchange fee applied
assert pricing["prompt"] == pytest.approx(0.001 * exchange_fee, rel=1e-9)
assert pricing["completion"] == pytest.approx(
0.002 * exchange_fee, rel=1e-9
)
assert pricing["request"] == pytest.approx(0.01 * exchange_fee, rel=1e-9)
assert pricing["image"] == pytest.approx(0.05 * exchange_fee, rel=1e-9)
assert pricing["web_search"] == pytest.approx(0.03 * exchange_fee, rel=1e-9)
assert pricing["internal_reasoning"] == pytest.approx(
0.015 * exchange_fee, rel=1e-9
)
# Verify max cost fields have exchange fee applied
assert pricing["max_prompt_cost"] == pytest.approx(
10.0 * exchange_fee, rel=1e-9
)
assert pricing["max_completion_cost"] == pytest.approx(
20.0 * exchange_fee, rel=1e-9
)
assert pricing["max_cost"] == pytest.approx(30.0 * exchange_fee, rel=1e-9)
def test_upstream_provider_fee_applied_to_usd_pricing(standard_model: Model) -> None:
"""Verify upstream_provider_fee is applied to all USD pricing fields."""
exchange_fee = 1.0 # No exchange fee
upstream_fee = 1.05 # 5% upstream provider fee
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(standard_model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# Verify all pricing fields have upstream fee applied
assert pricing["prompt"] == pytest.approx(0.001 * upstream_fee, rel=1e-9)
assert pricing["completion"] == pytest.approx(
0.002 * upstream_fee, rel=1e-9
)
assert pricing["request"] == pytest.approx(0.01 * upstream_fee, rel=1e-9)
assert pricing["image"] == pytest.approx(0.05 * upstream_fee, rel=1e-9)
assert pricing["web_search"] == pytest.approx(0.03 * upstream_fee, rel=1e-9)
assert pricing["internal_reasoning"] == pytest.approx(
0.015 * upstream_fee, rel=1e-9
)
# Verify max cost fields have upstream fee applied
assert pricing["max_prompt_cost"] == pytest.approx(
10.0 * upstream_fee, rel=1e-9
)
assert pricing["max_completion_cost"] == pytest.approx(
20.0 * upstream_fee, rel=1e-9
)
assert pricing["max_cost"] == pytest.approx(30.0 * upstream_fee, rel=1e-9)
# =============================================================================
# Combined Fee Application Tests (Core Issue #188 Fix)
# =============================================================================
def test_both_fees_compounded_correctly(standard_model: Model) -> None:
"""Test that exchange_fee and upstream_provider_fee are compounded (multiplied).
This is the PRIMARY test for issue #188. Before the fix, only exchange_fee was
applied to USD pricing. The fix ensures both fees are compounded correctly.
"""
exchange_fee = 1.005 # 0.5% exchange fee
upstream_fee = 1.05 # 5% upstream provider fee
expected_multiplier = exchange_fee * upstream_fee # 1.05525
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(standard_model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# All pricing fields should be multiplied by the combined fee
assert pricing["prompt"] == pytest.approx(
0.001 * expected_multiplier, rel=1e-9
)
assert pricing["completion"] == pytest.approx(
0.002 * expected_multiplier, rel=1e-9
)
assert pricing["request"] == pytest.approx(
0.01 * expected_multiplier, rel=1e-9
)
assert pricing["image"] == pytest.approx(
0.05 * expected_multiplier, rel=1e-9
)
assert pricing["web_search"] == pytest.approx(
0.03 * expected_multiplier, rel=1e-9
)
assert pricing["internal_reasoning"] == pytest.approx(
0.015 * expected_multiplier, rel=1e-9
)
# Max cost fields should also have combined fee applied
assert pricing["max_prompt_cost"] == pytest.approx(
10.0 * expected_multiplier, rel=1e-9
)
assert pricing["max_completion_cost"] == pytest.approx(
20.0 * expected_multiplier, rel=1e-9
)
assert pricing["max_cost"] == pytest.approx(
30.0 * expected_multiplier, rel=1e-9
)
def test_default_fee_values_from_settings(standard_model: Model) -> None:
"""Test with actual default fee values from settings.
Default values per routstr/core/settings.py:
- exchange_fee: 1.005 (0.5%)
- upstream_provider_fee: 1.05 (5%)
Combined: 1.05525 (5.525% total fee)
"""
# Use actual default values (don't mock)
payload = _model_to_row_payload(standard_model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# Calculate expected multiplier with production defaults
default_exchange_fee = 1.005
default_upstream_fee = 1.05
expected_multiplier = default_exchange_fee * default_upstream_fee # 1.05525
# Verify pricing is higher than original due to fees
assert pricing["prompt"] > standard_model.pricing.prompt
assert pricing["completion"] > standard_model.pricing.completion
assert pricing["request"] > standard_model.pricing.request
# Verify exact values with default fees
assert pricing["prompt"] == pytest.approx(0.001 * expected_multiplier, rel=1e-9)
assert pricing["completion"] == pytest.approx(0.002 * expected_multiplier, rel=1e-9)
assert pricing["request"] == pytest.approx(0.01 * expected_multiplier, rel=1e-9)
assert pricing["image"] == pytest.approx(0.05 * expected_multiplier, rel=1e-9)
assert pricing["web_search"] == pytest.approx(0.03 * expected_multiplier, rel=1e-9)
assert pricing["internal_reasoning"] == pytest.approx(
0.015 * expected_multiplier, rel=1e-9
)
# =============================================================================
# Varied Fee Scenarios
# =============================================================================
def test_higher_fee_values(standard_model: Model) -> None:
"""Test with significantly higher fee values to ensure scalability."""
exchange_fee = 1.02 # 2% exchange fee
upstream_fee = 1.15 # 15% upstream provider fee
expected_multiplier = exchange_fee * upstream_fee # 1.173
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(standard_model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
assert pricing["prompt"] == pytest.approx(
0.001 * expected_multiplier, rel=1e-9
)
assert pricing["completion"] == pytest.approx(
0.002 * expected_multiplier, rel=1e-9
)
assert pricing["request"] == pytest.approx(
0.01 * expected_multiplier, rel=1e-9
)
assert pricing["max_cost"] == pytest.approx(
30.0 * expected_multiplier, rel=1e-9
)
def test_minimal_fee_values(standard_model: Model) -> None:
"""Test with fees very close to 1.0 (minimal markup)."""
exchange_fee = 1.001 # 0.1% exchange fee
upstream_fee = 1.001 # 0.1% upstream provider fee
expected_multiplier = exchange_fee * upstream_fee # 1.002001
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(standard_model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# Verify precise calculation even with small fees
assert pricing["prompt"] == pytest.approx(
0.001 * expected_multiplier, rel=1e-9
)
assert pricing["completion"] == pytest.approx(
0.002 * expected_multiplier, rel=1e-9
)
def test_no_fees_applied(standard_model: Model) -> None:
"""Test with both fees set to 1.0 (no markup)."""
exchange_fee = 1.0 # No fee
upstream_fee = 1.0 # No fee
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(standard_model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# Prices should remain unchanged
assert pricing["prompt"] == pytest.approx(0.001, rel=1e-9)
assert pricing["completion"] == pytest.approx(0.002, rel=1e-9)
assert pricing["request"] == pytest.approx(0.01, rel=1e-9)
assert pricing["max_cost"] == pytest.approx(30.0, rel=1e-9)
# =============================================================================
# Zero and Edge Case Pricing Tests
# =============================================================================
def test_zero_value_pricing_fields(base_architecture: Architecture) -> None:
"""Verify that zero-value pricing fields are handled correctly.
Zero values should remain zero after fee application (0 * multiplier = 0).
"""
zero_pricing = Pricing(
prompt=0.0,
completion=0.0,
request=0.0,
image=0.0,
web_search=0.0,
internal_reasoning=0.0,
max_prompt_cost=0.0,
max_completion_cost=0.0,
max_cost=0.0,
)
model = Model(
id="test-zero-pricing",
name="Zero Pricing Model",
created=1234567890,
description="Model with all zero pricing",
context_length=8192,
architecture=base_architecture,
pricing=zero_pricing,
)
exchange_fee = 1.005
upstream_fee = 1.05
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# All values should remain zero
assert pricing["prompt"] == pytest.approx(0.0, abs=1e-9)
assert pricing["completion"] == pytest.approx(0.0, abs=1e-9)
assert pricing["request"] == pytest.approx(0.0, abs=1e-9)
assert pricing["image"] == pytest.approx(0.0, abs=1e-9)
assert pricing["web_search"] == pytest.approx(0.0, abs=1e-9)
assert pricing["internal_reasoning"] == pytest.approx(0.0, abs=1e-9)
assert pricing["max_prompt_cost"] == pytest.approx(0.0, abs=1e-9)
assert pricing["max_completion_cost"] == pytest.approx(0.0, abs=1e-9)
assert pricing["max_cost"] == pytest.approx(0.0, abs=1e-9)
def test_mixed_zero_and_nonzero_pricing(base_architecture: Architecture) -> None:
"""Test models with some zero and some non-zero pricing fields."""
mixed_pricing = Pricing(
prompt=0.001, # Non-zero
completion=0.002, # Non-zero
request=0.0, # Zero
image=0.0, # Zero
web_search=0.03, # Non-zero
internal_reasoning=0.0, # Zero
max_prompt_cost=10.0, # Non-zero
max_completion_cost=0.0, # Zero
max_cost=15.0, # Non-zero
)
model = Model(
id="test-mixed-pricing",
name="Mixed Pricing Model",
created=1234567890,
description="Model with mixed zero/non-zero pricing",
context_length=8192,
architecture=base_architecture,
pricing=mixed_pricing,
)
exchange_fee = 1.005
upstream_fee = 1.05
expected_multiplier = exchange_fee * upstream_fee
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# Non-zero values should have fees applied
assert pricing["prompt"] == pytest.approx(
0.001 * expected_multiplier, rel=1e-9
)
assert pricing["completion"] == pytest.approx(
0.002 * expected_multiplier, rel=1e-9
)
assert pricing["web_search"] == pytest.approx(
0.03 * expected_multiplier, rel=1e-9
)
assert pricing["max_prompt_cost"] == pytest.approx(
10.0 * expected_multiplier, rel=1e-9
)
assert pricing["max_cost"] == pytest.approx(
15.0 * expected_multiplier, rel=1e-9
)
# Zero values should remain zero
assert pricing["request"] == pytest.approx(0.0, abs=1e-9)
assert pricing["image"] == pytest.approx(0.0, abs=1e-9)
assert pricing["internal_reasoning"] == pytest.approx(0.0, abs=1e-9)
assert pricing["max_completion_cost"] == pytest.approx(0.0, abs=1e-9)
def test_very_small_pricing_values(base_architecture: Architecture) -> None:
"""Test with very small pricing values to verify precision."""
tiny_pricing = Pricing(
prompt=0.000001, # $0.000001 per token
completion=0.000002,
request=0.00001,
image=0.0001,
web_search=0.0001,
internal_reasoning=0.000001,
max_prompt_cost=0.01,
max_completion_cost=0.02,
max_cost=0.03,
)
model = Model(
id="test-tiny-pricing",
name="Tiny Pricing Model",
created=1234567890,
description="Model with very small pricing values",
context_length=8192,
architecture=base_architecture,
pricing=tiny_pricing,
)
exchange_fee = 1.005
upstream_fee = 1.05
expected_multiplier = exchange_fee * upstream_fee
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# Verify precision is maintained for very small values
assert pricing["prompt"] == pytest.approx(
0.000001 * expected_multiplier, rel=1e-6
)
assert pricing["completion"] == pytest.approx(
0.000002 * expected_multiplier, rel=1e-6
)
assert pricing["request"] == pytest.approx(
0.00001 * expected_multiplier, rel=1e-6
)
def test_very_large_pricing_values(base_architecture: Architecture) -> None:
"""Test with very large pricing values to ensure no overflow."""
large_pricing = Pricing(
prompt=100.0,
completion=200.0,
request=500.0,
image=1000.0,
web_search=750.0,
internal_reasoning=150.0,
max_prompt_cost=100000.0,
max_completion_cost=200000.0,
max_cost=500000.0,
)
model = Model(
id="test-large-pricing",
name="Large Pricing Model",
created=1234567890,
description="Model with very large pricing values",
context_length=8192,
architecture=base_architecture,
pricing=large_pricing,
)
exchange_fee = 1.005
upstream_fee = 1.05
expected_multiplier = exchange_fee * upstream_fee
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# Verify large values are handled correctly
assert pricing["prompt"] == pytest.approx(
100.0 * expected_multiplier, rel=1e-9
)
assert pricing["max_cost"] == pytest.approx(
500000.0 * expected_multiplier, rel=1e-9
)
# =============================================================================
# Sats Pricing Isolation Tests
# =============================================================================
def test_sats_pricing_not_modified(
base_architecture: Architecture, standard_pricing: Pricing
) -> None:
"""Verify that sats_pricing is NOT affected by USD fee application.
This ensures the fix doesn't break existing sats pricing behavior.
The fees should only be applied to USD pricing, not sats pricing.
"""
sats_pricing = Pricing(
prompt=10.0,
completion=20.0,
request=100.0,
image=500.0,
web_search=300.0,
internal_reasoning=150.0,
max_prompt_cost=10000.0,
max_completion_cost=20000.0,
max_cost=30000.0,
)
model = Model(
id="test-with-sats",
name="Model With Sats Pricing",
created=1234567890,
description="Model with both USD and sats pricing",
context_length=8192,
architecture=base_architecture,
pricing=standard_pricing,
sats_pricing=sats_pricing,
)
exchange_fee = 1.005
upstream_fee = 1.05
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(model)
sats_pricing_str = payload["sats_pricing"]
assert isinstance(sats_pricing_str, str)
sats_pricing_result = json.loads(sats_pricing_str)
# Sats pricing should be completely unchanged
assert sats_pricing_result["prompt"] == pytest.approx(10.0, rel=1e-9)
assert sats_pricing_result["completion"] == pytest.approx(20.0, rel=1e-9)
assert sats_pricing_result["request"] == pytest.approx(100.0, rel=1e-9)
assert sats_pricing_result["image"] == pytest.approx(500.0, rel=1e-9)
assert sats_pricing_result["web_search"] == pytest.approx(300.0, rel=1e-9)
assert sats_pricing_result["internal_reasoning"] == pytest.approx(
150.0, rel=1e-9
)
assert sats_pricing_result["max_prompt_cost"] == pytest.approx(
10000.0, rel=1e-9
)
assert sats_pricing_result["max_completion_cost"] == pytest.approx(
20000.0, rel=1e-9
)
assert sats_pricing_result["max_cost"] == pytest.approx(30000.0, rel=1e-9)
def test_model_without_sats_pricing(standard_model: Model) -> None:
"""Test models that don't have sats_pricing (None value)."""
assert standard_model.sats_pricing is None
exchange_fee = 1.005
upstream_fee = 1.05
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(standard_model)
# sats_pricing should remain None
assert payload["sats_pricing"] is None
# =============================================================================
# Payload Structure Tests
# =============================================================================
def test_payload_structure_unchanged(standard_model: Model) -> None:
"""Verify the database row payload structure is not corrupted by the fix."""
with patch.object(settings, "exchange_fee", 1.005):
with patch.object(settings, "upstream_provider_fee", 1.05):
payload = _model_to_row_payload(standard_model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
# Verify all expected keys exist
assert "id" in payload
assert "name" in payload
assert "created" in payload
assert "description" in payload
assert "context_length" in payload
assert "architecture" in payload
assert "pricing" in payload
assert "sats_pricing" in payload
assert "per_request_limits" in payload
assert "top_provider" in payload
# Verify types
assert isinstance(payload["id"], str)
assert isinstance(payload["name"], str)
assert isinstance(payload["created"], int)
assert isinstance(payload["description"], str)
assert isinstance(payload["context_length"], int)
assert isinstance(payload["architecture"], str) # JSON string
assert isinstance(payload["pricing"], str) # JSON string
assert payload["sats_pricing"] is None # None for this test model
# Verify JSON fields can be parsed
architecture_str = payload["architecture"]
assert isinstance(architecture_str, str)
architecture = json.loads(architecture_str)
pricing = json.loads(pricing_str)
assert isinstance(architecture, dict)
assert isinstance(pricing, dict)
# Verify pricing has all expected fields
expected_pricing_keys = {
"prompt",
"completion",
"request",
"image",
"web_search",
"internal_reasoning",
"max_prompt_cost",
"max_completion_cost",
"max_cost",
}
assert set(pricing.keys()) == expected_pricing_keys
def test_all_pricing_fields_present_after_fee_application(
standard_model: Model,
) -> None:
"""Ensure no pricing fields are accidentally dropped during fee application."""
with patch.object(settings, "exchange_fee", 1.005):
with patch.object(settings, "upstream_provider_fee", 1.05):
payload = _model_to_row_payload(standard_model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# All original pricing fields must be present
assert "prompt" in pricing
assert "completion" in pricing
assert "request" in pricing
assert "image" in pricing
assert "web_search" in pricing
assert "internal_reasoning" in pricing
assert "max_prompt_cost" in pricing
assert "max_completion_cost" in pricing
assert "max_cost" in pricing
# No extra fields should be added
assert len(pricing) == 9
# =============================================================================
# Consistency and Regression Tests
# =============================================================================
def test_fee_consistency_across_all_fields(standard_model: Model) -> None:
"""Verify the same fee multiplier is applied consistently to all fields."""
exchange_fee = 1.005
upstream_fee = 1.05
expected_multiplier = exchange_fee * upstream_fee
with patch.object(settings, "exchange_fee", exchange_fee):
with patch.object(settings, "upstream_provider_fee", upstream_fee):
payload = _model_to_row_payload(standard_model)
pricing_str = payload["pricing"]
assert isinstance(pricing_str, str)
pricing = json.loads(pricing_str)
# Calculate actual multipliers for each field
prompt_multiplier = pricing["prompt"] / standard_model.pricing.prompt
completion_multiplier = (
pricing["completion"] / standard_model.pricing.completion
)
request_multiplier = pricing["request"] / standard_model.pricing.request
image_multiplier = pricing["image"] / standard_model.pricing.image
web_search_multiplier = (
pricing["web_search"] / standard_model.pricing.web_search
)
internal_reasoning_multiplier = (
pricing["internal_reasoning"]
/ standard_model.pricing.internal_reasoning
)
max_prompt_multiplier = (
pricing["max_prompt_cost"] / standard_model.pricing.max_prompt_cost
)
max_completion_multiplier = (
pricing["max_completion_cost"]
/ standard_model.pricing.max_completion_cost
)
max_cost_multiplier = pricing["max_cost"] / standard_model.pricing.max_cost
# All multipliers should be identical and equal to expected multiplier
assert prompt_multiplier == pytest.approx(expected_multiplier, rel=1e-9)
assert completion_multiplier == pytest.approx(expected_multiplier, rel=1e-9)
assert request_multiplier == pytest.approx(expected_multiplier, rel=1e-9)
assert image_multiplier == pytest.approx(expected_multiplier, rel=1e-9)
assert web_search_multiplier == pytest.approx(expected_multiplier, rel=1e-9)
assert internal_reasoning_multiplier == pytest.approx(
expected_multiplier, rel=1e-9
)
assert max_prompt_multiplier == pytest.approx(expected_multiplier, rel=1e-9)
assert max_completion_multiplier == pytest.approx(
expected_multiplier, rel=1e-9
)
assert max_cost_multiplier == pytest.approx(expected_multiplier, rel=1e-9)
def test_multiple_calls_produce_consistent_results(standard_model: Model) -> None:
"""Verify that calling _model_to_row_payload multiple times is idempotent."""
with patch.object(settings, "exchange_fee", 1.005):
with patch.object(settings, "upstream_provider_fee", 1.05):
# Call multiple times
payload1 = _model_to_row_payload(standard_model)
payload2 = _model_to_row_payload(standard_model)
payload3 = _model_to_row_payload(standard_model)
pricing1_str = payload1["pricing"]
pricing2_str = payload2["pricing"]
pricing3_str = payload3["pricing"]
assert isinstance(pricing1_str, str)
assert isinstance(pricing2_str, str)
assert isinstance(pricing3_str, str)
pricing1 = json.loads(pricing1_str)
pricing2 = json.loads(pricing2_str)
pricing3 = json.loads(pricing3_str)
# All results should be identical
assert pricing1 == pricing2
assert pricing2 == pricing3
# Original model should not be mutated
assert standard_model.pricing.prompt == 0.001
assert standard_model.pricing.completion == 0.002
def test_original_model_not_mutated(standard_model: Model) -> None:
"""Ensure the original model object is not modified by fee application."""
original_prompt = standard_model.pricing.prompt
original_completion = standard_model.pricing.completion
original_max_cost = standard_model.pricing.max_cost
with patch.object(settings, "exchange_fee", 1.005):
with patch.object(settings, "upstream_provider_fee", 1.05):
_ = _model_to_row_payload(standard_model)
# Original model should be unchanged
assert standard_model.pricing.prompt == original_prompt
assert standard_model.pricing.completion == original_completion
assert standard_model.pricing.max_cost == original_max_cost