mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
182 lines
5.5 KiB
Python
182 lines
5.5 KiB
Python
|
|
from datetime import UTC, datetime
|
||
|
|
from types import SimpleNamespace
|
||
|
|
from unittest.mock import AsyncMock
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from api.services.pricing import workflow_run_cost as workflow_run_cost_mod
|
||
|
|
from api.services.pricing.workflow_run_cost import (
|
||
|
|
apply_usage_delta_to_organization,
|
||
|
|
build_workflow_run_cost_info,
|
||
|
|
calculate_workflow_run_cost,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _make_workflow_run():
|
||
|
|
return SimpleNamespace(
|
||
|
|
id=123,
|
||
|
|
workflow_id=456,
|
||
|
|
mode="textchat",
|
||
|
|
created_at=datetime.now(UTC),
|
||
|
|
usage_info={
|
||
|
|
"llm": {},
|
||
|
|
"tts": {},
|
||
|
|
"stt": {},
|
||
|
|
"call_duration_seconds": 7,
|
||
|
|
},
|
||
|
|
cost_info={},
|
||
|
|
workflow=SimpleNamespace(
|
||
|
|
organization_id=42,
|
||
|
|
user=SimpleNamespace(selected_organization_id=42),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_build_workflow_run_cost_info_does_not_update_org_usage(monkeypatch):
|
||
|
|
workflow_run = _make_workflow_run()
|
||
|
|
get_org = AsyncMock(return_value=SimpleNamespace(id=42, price_per_second_usd=1.5))
|
||
|
|
update_usage = AsyncMock()
|
||
|
|
|
||
|
|
monkeypatch.setattr(
|
||
|
|
workflow_run_cost_mod.db_client, "get_organization_by_id", get_org
|
||
|
|
)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
workflow_run_cost_mod.db_client, "update_usage_after_run", update_usage
|
||
|
|
)
|
||
|
|
|
||
|
|
cost_info = await build_workflow_run_cost_info(workflow_run)
|
||
|
|
|
||
|
|
assert cost_info is not None
|
||
|
|
assert cost_info["call_duration_seconds"] == 7
|
||
|
|
assert "cost_breakdown" in cost_info
|
||
|
|
assert "dograh_token_usage" in cost_info
|
||
|
|
assert cost_info["charge_usd"] == 10.5
|
||
|
|
update_usage.assert_not_called()
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_calculate_workflow_run_cost_keeps_org_usage_side_effect_in_wrapper(
|
||
|
|
monkeypatch,
|
||
|
|
):
|
||
|
|
workflow_run = _make_workflow_run()
|
||
|
|
get_org = AsyncMock(return_value=SimpleNamespace(id=42, price_per_second_usd=None))
|
||
|
|
update_run = AsyncMock()
|
||
|
|
update_usage = AsyncMock()
|
||
|
|
|
||
|
|
monkeypatch.setattr(
|
||
|
|
workflow_run_cost_mod.db_client,
|
||
|
|
"get_workflow_run_by_id",
|
||
|
|
AsyncMock(return_value=workflow_run),
|
||
|
|
)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
workflow_run_cost_mod.db_client, "get_organization_by_id", get_org
|
||
|
|
)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
workflow_run_cost_mod.db_client, "update_workflow_run", update_run
|
||
|
|
)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
workflow_run_cost_mod.db_client, "update_usage_after_run", update_usage
|
||
|
|
)
|
||
|
|
|
||
|
|
await calculate_workflow_run_cost(workflow_run.id)
|
||
|
|
|
||
|
|
update_run.assert_awaited_once()
|
||
|
|
saved_kwargs = update_run.await_args.kwargs
|
||
|
|
assert saved_kwargs["run_id"] == workflow_run.id
|
||
|
|
assert "cost_breakdown" in saved_kwargs["cost_info"]
|
||
|
|
update_usage.assert_awaited_once()
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_apply_usage_delta_to_organization_uses_incremental_costs(
|
||
|
|
monkeypatch,
|
||
|
|
):
|
||
|
|
workflow_run = _make_workflow_run()
|
||
|
|
workflow_run.cost_info = {"call_id": "preserve-me"}
|
||
|
|
|
||
|
|
usage_delta_one = {
|
||
|
|
"llm": {
|
||
|
|
"OpenAILLMService#0|||gpt-4.1-mini": {
|
||
|
|
"prompt_tokens": 1_000,
|
||
|
|
"completion_tokens": 100,
|
||
|
|
"total_tokens": 1_100,
|
||
|
|
"cache_read_input_tokens": 0,
|
||
|
|
"cache_creation_input_tokens": 0,
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"tts": {},
|
||
|
|
"stt": {},
|
||
|
|
"call_duration_seconds": 3,
|
||
|
|
}
|
||
|
|
usage_delta_two = {
|
||
|
|
"llm": {
|
||
|
|
"OpenAILLMService#0|||gpt-4.1-mini": {
|
||
|
|
"prompt_tokens": 2_000,
|
||
|
|
"completion_tokens": 50,
|
||
|
|
"total_tokens": 2_050,
|
||
|
|
"cache_read_input_tokens": 0,
|
||
|
|
"cache_creation_input_tokens": 0,
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"tts": {},
|
||
|
|
"stt": {},
|
||
|
|
"call_duration_seconds": 4,
|
||
|
|
}
|
||
|
|
merged_usage = {
|
||
|
|
"llm": {
|
||
|
|
"OpenAILLMService#0|||gpt-4.1-mini": {
|
||
|
|
"prompt_tokens": 3_000,
|
||
|
|
"completion_tokens": 150,
|
||
|
|
"total_tokens": 3_150,
|
||
|
|
"cache_read_input_tokens": 0,
|
||
|
|
"cache_creation_input_tokens": 0,
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"tts": {},
|
||
|
|
"stt": {},
|
||
|
|
"call_duration_seconds": 7,
|
||
|
|
}
|
||
|
|
|
||
|
|
get_org = AsyncMock(return_value=SimpleNamespace(id=42, price_per_second_usd=1.5))
|
||
|
|
update_usage = AsyncMock()
|
||
|
|
|
||
|
|
monkeypatch.setattr(
|
||
|
|
workflow_run_cost_mod.db_client, "get_organization_by_id", get_org
|
||
|
|
)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
workflow_run_cost_mod.db_client, "update_usage_after_run", update_usage
|
||
|
|
)
|
||
|
|
|
||
|
|
first_delta = await apply_usage_delta_to_organization(workflow_run, usage_delta_one)
|
||
|
|
second_delta = await apply_usage_delta_to_organization(
|
||
|
|
workflow_run, usage_delta_two
|
||
|
|
)
|
||
|
|
total_workflow_run = SimpleNamespace(**workflow_run.__dict__)
|
||
|
|
total_workflow_run.usage_info = merged_usage
|
||
|
|
total_cost = await build_workflow_run_cost_info(total_workflow_run)
|
||
|
|
|
||
|
|
assert first_delta is not None
|
||
|
|
assert second_delta is not None
|
||
|
|
assert total_cost is not None
|
||
|
|
assert update_usage.await_count == 2
|
||
|
|
assert update_usage.await_args_list[0].args == (
|
||
|
|
42,
|
||
|
|
first_delta["dograh_token_usage"],
|
||
|
|
3.0,
|
||
|
|
first_delta["charge_usd"],
|
||
|
|
)
|
||
|
|
assert update_usage.await_args_list[1].args == (
|
||
|
|
42,
|
||
|
|
second_delta["dograh_token_usage"],
|
||
|
|
4.0,
|
||
|
|
second_delta["charge_usd"],
|
||
|
|
)
|
||
|
|
assert (
|
||
|
|
first_delta["dograh_token_usage"] + second_delta["dograh_token_usage"]
|
||
|
|
) == pytest.approx(total_cost["dograh_token_usage"])
|
||
|
|
assert (
|
||
|
|
first_delta["charge_usd"] + second_delta["charge_usd"]
|
||
|
|
== total_cost["charge_usd"]
|
||
|
|
)
|