"""Tests for the ace.tracing wrapper.""" from __future__ import annotations import os from unittest.mock import patch import pytest @pytest.mark.unit class TestConfigure: """Tests for ace.tracing.configure().""" def test_configure_sets_tracking_uri_and_token(self) -> None: with patch.dict(os.environ, {}, clear=False): from kayba_tracing._wrapper import configure with patch("kayba_tracing._wrapper.mlflow") as mock_mlflow: configure(api_key="kb-test-key") mock_mlflow.set_tracking_uri.assert_called_once_with( "https://use.kayba.ai/api/mlflow" ) assert os.environ["MLFLOW_TRACKING_TOKEN"] == "kb-test-key" def test_configure_custom_base_url(self) -> None: with patch.dict(os.environ, {}, clear=False): from kayba_tracing._wrapper import configure with patch("kayba_tracing._wrapper.mlflow") as mock_mlflow: configure( api_key="kb-test-key", base_url="https://custom.example.com", ) mock_mlflow.set_tracking_uri.assert_called_once_with( "https://custom.example.com/api/mlflow" ) def test_configure_strips_trailing_slash(self) -> None: with patch.dict(os.environ, {}, clear=False): from kayba_tracing._wrapper import configure with patch("kayba_tracing._wrapper.mlflow") as mock_mlflow: configure( api_key="kb-test-key", base_url="https://custom.example.com/", ) mock_mlflow.set_tracking_uri.assert_called_once_with( "https://custom.example.com/api/mlflow" ) def test_configure_reads_api_key_from_env(self) -> None: with patch.dict(os.environ, {"KAYBA_API_KEY": "kb-env-key"}, clear=False): from kayba_tracing._wrapper import configure with patch("kayba_tracing._wrapper.mlflow"): configure() assert os.environ["MLFLOW_TRACKING_TOKEN"] == "kb-env-key" def test_configure_reads_base_url_from_env(self) -> None: with patch.dict( os.environ, { "KAYBA_API_KEY": "kb-key", "KAYBA_API_URL": "https://env.example.com", }, clear=False, ): from kayba_tracing._wrapper import configure with patch("kayba_tracing._wrapper.mlflow") as mock_mlflow: configure() mock_mlflow.set_tracking_uri.assert_called_once_with( "https://env.example.com/api/mlflow" ) def test_configure_raises_without_api_key(self) -> None: with patch.dict(os.environ, {"KAYBA_API_KEY": ""}, clear=False): from kayba_tracing._wrapper import configure with pytest.raises(ValueError, match="No API key provided"): configure() def test_experiment_is_alias_for_folder(self) -> None: import kayba_tracing._wrapper as w with patch.dict(os.environ, {}, clear=False): with patch("kayba_tracing._wrapper.mlflow"): w.configure(api_key="kb-key", experiment="my-project") assert w._folder == "my-project" def test_folder_takes_precedence_over_experiment(self) -> None: import kayba_tracing._wrapper as w with patch.dict(os.environ, {}, clear=False): with patch("kayba_tracing._wrapper.mlflow"): w.configure( api_key="kb-key", experiment="from-experiment", folder="from-folder", ) assert w._folder == "from-folder" def test_configure_sets_folder(self) -> None: import kayba_tracing._wrapper as w with patch.dict(os.environ, {}, clear=False): with patch("kayba_tracing._wrapper.mlflow"): w.configure(api_key="kb-key", folder="my-folder") assert w._folder == "my-folder" def test_configure_clears_folder_when_none(self) -> None: import kayba_tracing._wrapper as w with patch.dict(os.environ, {}, clear=False): with patch("kayba_tracing._wrapper.mlflow"): w.configure(api_key="kb-key", folder="old") w.configure(api_key="kb-key") assert w._folder is None @pytest.mark.unit class TestSanitizeFolder: """Tests for folder name sanitization.""" def test_strips_html_tags(self) -> None: from kayba_tracing._wrapper import _sanitize_folder assert _sanitize_folder('') == "alertxss" def test_strips_control_characters(self) -> None: from kayba_tracing._wrapper import _sanitize_folder assert _sanitize_folder("folder\x00\x1f\nname") == "foldername" def test_allows_safe_characters(self) -> None: from kayba_tracing._wrapper import _sanitize_folder assert _sanitize_folder("my-folder/sub_dir 2.0") == "my-folder/sub_dir 2.0" def test_truncates_long_names(self) -> None: from kayba_tracing._wrapper import _sanitize_folder assert len(_sanitize_folder("a" * 500)) == 256 def test_strips_sql_injection_chars(self) -> None: from kayba_tracing._wrapper import _sanitize_folder assert _sanitize_folder("folder'; DROP TABLE--") == "folder DROP TABLE--" def test_configure_sanitizes_folder(self) -> None: import kayba_tracing._wrapper as w with patch.dict(os.environ, {}, clear=False): with patch("kayba_tracing._wrapper.mlflow"): w.configure(api_key="kb-key", folder='') # Entire input is an HTML tag, stripped to empty string assert w._folder is None def test_set_folder_sanitizes(self) -> None: import kayba_tracing._wrapper as w w.set_folder("bold") assert w.get_folder() == "bold" @pytest.mark.unit class TestFolder: """Tests for set_folder / get_folder.""" def test_set_and_get_folder(self) -> None: import kayba_tracing._wrapper as w w.set_folder("production") assert w.get_folder() == "production" def test_clear_folder(self) -> None: import kayba_tracing._wrapper as w w.set_folder("production") w.set_folder(None) assert w.get_folder() is None def test_inject_folder_tag(self) -> None: import kayba_tracing._wrapper as w w._folder = "my-folder" with patch("kayba_tracing._wrapper.mlflow") as mock_mlflow: w._inject_folder_tag() mock_mlflow.update_current_trace.assert_called_once_with( tags={"kayba.folder": "my-folder"} ) def test_inject_folder_tag_noop_when_none(self) -> None: import kayba_tracing._wrapper as w w._folder = None with patch("kayba_tracing._wrapper.mlflow") as mock_mlflow: w._inject_folder_tag() mock_mlflow.update_current_trace.assert_not_called() @pytest.mark.unit class TestTraceDecorator: """Tests for the trace decorator wrapper.""" def test_trace_wraps_function(self) -> None: import kayba_tracing._wrapper as w w._folder = "test-folder" with patch("kayba_tracing._wrapper.mlflow") as mock_mlflow: # Make mlflow.trace return a passthrough decorator mock_mlflow.trace.side_effect = lambda fn=None, **kw: ( fn if fn is not None else (lambda f: f) ) @w.trace def my_func(x: int) -> int: return x + 1 result = my_func(5) assert result == 6 mock_mlflow.update_current_trace.assert_called_with( tags={"kayba.folder": "test-folder"} ) def test_trace_with_params(self) -> None: import kayba_tracing._wrapper as w w._folder = None with patch("kayba_tracing._wrapper.mlflow") as mock_mlflow: mock_mlflow.trace.return_value = lambda fn: fn @w.trace(name="custom", span_type="LLM") def my_func() -> str: return "ok" result = my_func() assert result == "ok" mock_mlflow.trace.assert_called_once_with(name="custom", span_type="LLM") # No folder set, so no tag injection mock_mlflow.update_current_trace.assert_not_called() @pytest.mark.unit class TestReExports: """Verify utility re-exports.""" def test_enable_calls_mlflow(self) -> None: from kayba_tracing._wrapper import enable with patch("kayba_tracing._wrapper.mlflow.tracing.enable") as mock: enable() mock.assert_called_once() def test_disable_calls_mlflow(self) -> None: from kayba_tracing._wrapper import disable with patch("kayba_tracing._wrapper.mlflow.tracing.disable") as mock: disable() mock.assert_called_once() @pytest.mark.unit class TestPackageInit: """Verify the public __init__ exports.""" def test_all_exports(self) -> None: import ace.tracing expected = { "configure", "disable", "enable", "get_folder", "get_trace", "search_traces", "set_folder", "start_span", "trace", } assert set(ace.tracing.__all__) == expected