# tests/tasks/test_cost_tasks.py """ Tests for cost tracking and budget management tasks. These tests verify: - Task signatures are correctly defined - Tasks are bound (have access to self) - Tasks return expected structure - Tasks follow ADR-012 (multi-layered cost tracking) Note: These tests mock actual execution since they would require database access and Redis operations in production. """ import uuid from unittest.mock import patch class TestAggregateDailyCostsTask: """Tests for the aggregate_daily_costs task.""" def test_aggregate_daily_costs_task_exists(self): """Test that aggregate_daily_costs task is registered.""" import app.tasks.cost # noqa: F401 from app.celery_app import celery_app assert "app.tasks.cost.aggregate_daily_costs" in celery_app.tasks def test_aggregate_daily_costs_is_bound_task(self): """Test that aggregate_daily_costs is a bound task.""" from app.tasks.cost import aggregate_daily_costs assert aggregate_daily_costs.__bound__ is True def test_aggregate_daily_costs_has_correct_name(self): """Test that aggregate_daily_costs has the correct task name.""" from app.tasks.cost import aggregate_daily_costs assert aggregate_daily_costs.name == "app.tasks.cost.aggregate_daily_costs" def test_aggregate_daily_costs_returns_expected_structure(self): """Test that aggregate_daily_costs returns expected result.""" from app.tasks.cost import aggregate_daily_costs result = aggregate_daily_costs() assert isinstance(result, dict) assert "status" in result assert result["status"] == "pending" class TestCheckBudgetThresholdsTask: """Tests for the check_budget_thresholds task.""" def test_check_budget_thresholds_task_exists(self): """Test that check_budget_thresholds task is registered.""" import app.tasks.cost # noqa: F401 from app.celery_app import celery_app assert "app.tasks.cost.check_budget_thresholds" in celery_app.tasks def test_check_budget_thresholds_is_bound_task(self): """Test that check_budget_thresholds is a bound task.""" from app.tasks.cost import check_budget_thresholds assert check_budget_thresholds.__bound__ is True def test_check_budget_thresholds_returns_expected_structure(self): """Test that check_budget_thresholds returns expected result.""" from app.tasks.cost import check_budget_thresholds project_id = str(uuid.uuid4()) result = check_budget_thresholds(project_id) assert isinstance(result, dict) assert "status" in result assert "project_id" in result assert result["project_id"] == project_id class TestRecordLlmUsageTask: """Tests for the record_llm_usage task.""" def test_record_llm_usage_task_exists(self): """Test that record_llm_usage task is registered.""" import app.tasks.cost # noqa: F401 from app.celery_app import celery_app assert "app.tasks.cost.record_llm_usage" in celery_app.tasks def test_record_llm_usage_is_bound_task(self): """Test that record_llm_usage is a bound task.""" from app.tasks.cost import record_llm_usage assert record_llm_usage.__bound__ is True def test_record_llm_usage_returns_expected_structure(self): """Test that record_llm_usage returns expected result.""" from app.tasks.cost import record_llm_usage agent_id = str(uuid.uuid4()) project_id = str(uuid.uuid4()) model = "claude-opus-4-5-20251101" prompt_tokens = 1500 completion_tokens = 500 cost_usd = 0.0825 result = record_llm_usage( agent_id, project_id, model, prompt_tokens, completion_tokens, cost_usd ) assert isinstance(result, dict) assert "status" in result assert "agent_id" in result assert "project_id" in result assert "cost_usd" in result assert result["agent_id"] == agent_id assert result["project_id"] == project_id assert result["cost_usd"] == cost_usd def test_record_llm_usage_with_different_models(self): """Test that record_llm_usage handles different model types.""" from app.tasks.cost import record_llm_usage agent_id = str(uuid.uuid4()) project_id = str(uuid.uuid4()) models = [ ("claude-opus-4-5-20251101", 0.015), ("claude-sonnet-4-20250514", 0.003), ("gpt-4-turbo", 0.01), ("gemini-1.5-pro", 0.007), ] for model, cost in models: result = record_llm_usage(agent_id, project_id, model, 1000, 500, cost) assert result["status"] == "pending" def test_record_llm_usage_with_zero_tokens(self): """Test that record_llm_usage handles zero token counts.""" from app.tasks.cost import record_llm_usage agent_id = str(uuid.uuid4()) project_id = str(uuid.uuid4()) result = record_llm_usage( agent_id, project_id, "claude-opus-4-5-20251101", 0, 0, 0.0 ) assert result["status"] == "pending" class TestGenerateCostReportTask: """Tests for the generate_cost_report task.""" def test_generate_cost_report_task_exists(self): """Test that generate_cost_report task is registered.""" import app.tasks.cost # noqa: F401 from app.celery_app import celery_app assert "app.tasks.cost.generate_cost_report" in celery_app.tasks def test_generate_cost_report_is_bound_task(self): """Test that generate_cost_report is a bound task.""" from app.tasks.cost import generate_cost_report assert generate_cost_report.__bound__ is True def test_generate_cost_report_returns_expected_structure(self): """Test that generate_cost_report returns expected result.""" from app.tasks.cost import generate_cost_report project_id = str(uuid.uuid4()) start_date = "2025-01-01" end_date = "2025-01-31" result = generate_cost_report(project_id, start_date, end_date) assert isinstance(result, dict) assert "status" in result assert "project_id" in result assert "start_date" in result assert "end_date" in result assert result["project_id"] == project_id assert result["start_date"] == start_date assert result["end_date"] == end_date def test_generate_cost_report_with_various_date_ranges(self): """Test that generate_cost_report handles various date ranges.""" from app.tasks.cost import generate_cost_report project_id = str(uuid.uuid4()) date_ranges = [ ("2025-01-01", "2025-01-01"), # Single day ("2025-01-01", "2025-01-07"), # Week ("2025-01-01", "2025-12-31"), # Full year ] for start, end in date_ranges: result = generate_cost_report(project_id, start, end) assert result["status"] == "pending" class TestResetDailyBudgetCountersTask: """Tests for the reset_daily_budget_counters task.""" def test_reset_daily_budget_counters_task_exists(self): """Test that reset_daily_budget_counters task is registered.""" import app.tasks.cost # noqa: F401 from app.celery_app import celery_app assert "app.tasks.cost.reset_daily_budget_counters" in celery_app.tasks def test_reset_daily_budget_counters_is_bound_task(self): """Test that reset_daily_budget_counters is a bound task.""" from app.tasks.cost import reset_daily_budget_counters assert reset_daily_budget_counters.__bound__ is True def test_reset_daily_budget_counters_returns_expected_structure(self): """Test that reset_daily_budget_counters returns expected result.""" from app.tasks.cost import reset_daily_budget_counters result = reset_daily_budget_counters() assert isinstance(result, dict) assert "status" in result assert result["status"] == "pending" class TestCostTaskRouting: """Tests for cost task queue routing.""" def test_cost_tasks_route_to_default_queue(self): """Test that cost tasks route to 'default' queue. Per the routing configuration, cost tasks match 'app.tasks.*' which routes to the default queue. """ from app.celery_app import celery_app routes = celery_app.conf.task_routes # Cost tasks match the generic 'app.tasks.*' pattern assert "app.tasks.*" in routes assert routes["app.tasks.*"]["queue"] == "default" def test_all_cost_tasks_match_routing_pattern(self): """Test that all cost task names match the routing pattern.""" task_names = [ "app.tasks.cost.aggregate_daily_costs", "app.tasks.cost.check_budget_thresholds", "app.tasks.cost.record_llm_usage", "app.tasks.cost.generate_cost_report", "app.tasks.cost.reset_daily_budget_counters", ] for name in task_names: assert name.startswith("app.tasks.") class TestCostTaskLogging: """Tests for cost task logging behavior.""" def test_aggregate_daily_costs_logs_execution(self): """Test that aggregate_daily_costs logs when executed.""" from app.tasks.cost import aggregate_daily_costs with patch("app.tasks.cost.logger") as mock_logger: aggregate_daily_costs() mock_logger.info.assert_called_once() call_args = mock_logger.info.call_args[0][0] assert "cost" in call_args.lower() or "aggregat" in call_args.lower() def test_check_budget_thresholds_logs_execution(self): """Test that check_budget_thresholds logs when executed.""" from app.tasks.cost import check_budget_thresholds project_id = str(uuid.uuid4()) with patch("app.tasks.cost.logger") as mock_logger: check_budget_thresholds(project_id) mock_logger.info.assert_called_once() call_args = mock_logger.info.call_args[0][0] assert project_id in call_args def test_record_llm_usage_logs_execution(self): """Test that record_llm_usage logs when executed.""" from app.tasks.cost import record_llm_usage agent_id = str(uuid.uuid4()) project_id = str(uuid.uuid4()) model = "claude-opus-4-5-20251101" with patch("app.tasks.cost.logger") as mock_logger: record_llm_usage(agent_id, project_id, model, 100, 50, 0.01) # Uses debug level, not info mock_logger.debug.assert_called_once() call_args = mock_logger.debug.call_args[0][0] assert model in call_args def test_generate_cost_report_logs_execution(self): """Test that generate_cost_report logs when executed.""" from app.tasks.cost import generate_cost_report project_id = str(uuid.uuid4()) with patch("app.tasks.cost.logger") as mock_logger: generate_cost_report(project_id, "2025-01-01", "2025-01-31") mock_logger.info.assert_called_once() call_args = mock_logger.info.call_args[0][0] assert project_id in call_args def test_reset_daily_budget_counters_logs_execution(self): """Test that reset_daily_budget_counters logs when executed.""" from app.tasks.cost import reset_daily_budget_counters with patch("app.tasks.cost.logger") as mock_logger: reset_daily_budget_counters() mock_logger.info.assert_called_once() call_args = mock_logger.info.call_args[0][0] assert "reset" in call_args.lower() or "counter" in call_args.lower() class TestCostTaskSignatures: """Tests for cost task signature creation.""" def test_record_llm_usage_signature_creation(self): """Test that record_llm_usage signature can be created.""" from app.tasks.cost import record_llm_usage agent_id = str(uuid.uuid4()) project_id = str(uuid.uuid4()) sig = record_llm_usage.s( agent_id, project_id, "claude-opus-4-5-20251101", 100, 50, 0.01 ) assert sig is not None assert len(sig.args) == 6 def test_check_budget_thresholds_signature_creation(self): """Test that check_budget_thresholds signature can be created.""" from app.tasks.cost import check_budget_thresholds project_id = str(uuid.uuid4()) sig = check_budget_thresholds.s(project_id) assert sig is not None assert sig.args == (project_id,) def test_cost_task_chain_creation(self): """Test that cost tasks can be chained together.""" from celery import chain from app.tasks.cost import check_budget_thresholds, record_llm_usage agent_id = str(uuid.uuid4()) project_id = str(uuid.uuid4()) # Build a chain: record usage, then check thresholds workflow = chain( record_llm_usage.s( agent_id, project_id, "claude-opus-4-5-20251101", 1000, 500, 0.05 ), check_budget_thresholds.s(project_id), ) assert workflow is not None