import os import sys from datetime import datetime from unittest.mock import MagicMock, patch import polars as pl import pytest sys.path.insert(0, os.path.abspath("../../../..")) from litellm.integrations.cloudzero.transform import CBFTransformer from litellm.types.integrations.cloudzero import CBFRecord class TestCBFTransformer: """Test suite for CBFTransformer class.""" def test_init(self): """Test CBFTransformer initialization.""" transformer = CBFTransformer() assert hasattr(transformer, 'czrn_generator') assert transformer.czrn_generator is not None def test_transform_empty_dataframe(self): """Test transform method with empty DataFrame.""" transformer = CBFTransformer() empty_df = pl.DataFrame() result = transformer.transform(empty_df) assert result.is_empty() assert isinstance(result, pl.DataFrame) def test_transform_with_zero_successful_requests(self): """Test transform method filters out records with zero successful_requests.""" transformer = CBFTransformer() data = pl.DataFrame({ 'date': ['2025-01-19'], 'successful_requests': [0], 'spend': [10.0], 'entity_id': ['test_entity'], 'model': ['gpt-4'] }) result = transformer.transform(data) assert result.is_empty() def test_transform_with_valid_data(self): """Test transform method with valid data.""" transformer = CBFTransformer() with patch.object(transformer, '_create_cbf_record') as mock_create: mock_create.return_value = CBFRecord({'test': 'data'}) data = pl.DataFrame({ 'date': ['2025-01-19'], 'successful_requests': [5], 'spend': [10.0], 'entity_id': ['test_entity'], 'model': ['gpt-4'] }) result = transformer.transform(data) assert len(result) == 1 mock_create.assert_called_once() def test_transform_handles_czrn_generation_failures(self): """Test transform method handles CZRN generation failures gracefully.""" transformer = CBFTransformer() with patch.object(transformer, '_create_cbf_record') as mock_create: mock_create.side_effect = Exception("CZRN generation failed") data = pl.DataFrame({ 'date': ['2025-01-19'], 'successful_requests': [5], 'spend': [10.0], 'entity_id': ['test_entity'], 'model': ['gpt-4'] }) result = transformer.transform(data) assert result.is_empty() def test_create_cbf_record(self): """Test _create_cbf_record method with valid row data.""" transformer = CBFTransformer() with patch.object(transformer.czrn_generator, 'create_from_litellm_data') as mock_czrn, \ patch.object(transformer.czrn_generator, 'extract_components') as mock_extract: mock_czrn.return_value = 'test-czrn' mock_extract.return_value = ('service', 'provider', 'region', 'account', 'resource', 'local_id') row = { 'date': '2025-01-19', 'spend': 10.5, 'prompt_tokens': 100, 'completion_tokens': 50, 'entity_id': 'test_entity', 'model': 'gpt-4', 'entity_type': 'user', 'model_group': 'openai', 'custom_llm_provider': 'openai', 'api_key': 'sk-test123', 'api_requests': 5, 'successful_requests': 5, 'failed_requests': 0 } result = transformer._create_cbf_record(row) assert isinstance(result, CBFRecord) assert result['cost/cost'] == 10.5 assert result['usage/amount'] == 150 # 100 + 50 assert result['usage/units'] == 'tokens' assert result['resource/id'] == 'test-czrn' def test_create_cbf_record_adds_user_email_tag(self): """Test that user_email field is emitted as a resource tag when present.""" transformer = CBFTransformer() with patch.object(transformer.czrn_generator, 'create_from_litellm_data') as mock_czrn, \ patch.object(transformer.czrn_generator, 'extract_components') as mock_extract: mock_czrn.return_value = 'test-czrn' mock_extract.return_value = ('service', 'provider', 'region', 'account', 'resource', 'local_id') row = { 'date': '2025-01-19', 'spend': 1.0, 'prompt_tokens': 10, 'completion_tokens': 5, 'model': 'gpt-4', 'api_key': 'sk-useremail', 'team_id': 'team-123', 'team_alias': 'Dev Team', 'user_email': 'user@example.com' } result = transformer._create_cbf_record(row) assert result['resource/tag:user_email'] == 'user@example.com' def test_create_cbf_record_omits_empty_user_email(self): """Test that empty user_email values are not added as resource tags.""" transformer = CBFTransformer() with patch.object(transformer.czrn_generator, 'create_from_litellm_data') as mock_czrn, \ patch.object(transformer.czrn_generator, 'extract_components') as mock_extract: mock_czrn.return_value = 'test-czrn' mock_extract.return_value = ('service', 'provider', 'region', 'account', 'resource', 'local_id') row = { 'date': '2025-01-19', 'spend': 1.0, 'prompt_tokens': 10, 'completion_tokens': 5, 'model': 'gpt-4', 'api_key': 'sk-useremail', 'team_id': 'team-123', 'team_alias': 'Dev Team', 'user_email': None } result = transformer._create_cbf_record(row) assert 'resource/tag:user_email' not in result def test_create_cbf_record_minimal_data(self): """Test _create_cbf_record method with minimal row data.""" transformer = CBFTransformer() with patch.object(transformer.czrn_generator, 'create_from_litellm_data') as mock_czrn, \ patch.object(transformer.czrn_generator, 'extract_components') as mock_extract: mock_czrn.return_value = 'test-czrn' mock_extract.return_value = ('service', 'provider', 'region', 'account', 'resource', 'local_id') row = { 'date': '2025-01-19', 'spend': 0.0 } result = transformer._create_cbf_record(row) assert isinstance(result, CBFRecord) assert result['cost/cost'] == 0.0 assert result['usage/amount'] == 0 # no tokens assert result['usage/units'] == 'tokens' def test_parse_date_with_valid_string(self): """Test _parse_date method with valid date string.""" transformer = CBFTransformer() result = transformer._parse_date('2025-01-19') assert isinstance(result, datetime) assert result.year == 2025 assert result.month == 1 assert result.day == 19 def test_parse_date_with_datetime_object(self): """Test _parse_date method with datetime object.""" transformer = CBFTransformer() dt = datetime(2025, 1, 19) result = transformer._parse_date(dt) assert result == dt def test_parse_date_with_none(self): """Test _parse_date method with None.""" transformer = CBFTransformer() result = transformer._parse_date(None) assert result is None def test_parse_date_with_invalid_string(self): """Test _parse_date method with invalid date string.""" transformer = CBFTransformer() result = transformer._parse_date('invalid-date') assert result is None def test_parse_date_with_iso_format(self): """Test _parse_date method with ISO format string.""" transformer = CBFTransformer() result = transformer._parse_date('2025-01-19T10:30:00Z') assert isinstance(result, datetime) assert result.year == 2025