"""Tests for ``build_tool_error_log_extra``. Locks the Datadog-facet contract shared by the MCP and worker transports. Field names are load-bearing for dashboards. """ import pytest from arcade_core.errors import ErrorKind from arcade_core.log_extras import build_tool_error_log_extra, build_tool_error_span_attributes from arcade_core.schema import ToolCallError def _err(**kw): defaults = { "message": "Spreadsheet not found", "kind": ErrorKind.TOOL_RUNTIME_FATAL, "developer_message": None, "status_code": 404, "can_retry": False, } defaults.update(kw) return ToolCallError(**defaults) def test_canonical_field_contract(): """Locks the canonical field names every Datadog dashboard depends on.""" extra = build_tool_error_log_extra(_err(developer_message="dev: x"), tool_name="MyTool") assert extra == { "error_kind": "TOOL_RUNTIME_FATAL", "error_message": "Spreadsheet not found", "error_developer_message": "dev: x", "error_status_code": 404, "error_can_retry": False, "tool_name": "MyTool", } def test_kind_enum_value_used_not_repr(): """``error_kind`` must be the stable string code (Datadog facets on ``"TOOL_RUNTIME_FATAL"``, NOT the Python repr ``"ErrorKind.TOOL_RUNTIME_FATAL"``).""" extra = build_tool_error_log_extra( _err(kind=ErrorKind.UPSTREAM_RUNTIME_RATE_LIMIT), tool_name="t" ) assert extra["error_kind"] == "UPSTREAM_RUNTIME_RATE_LIMIT" assert "ErrorKind." not in extra["error_kind"] def test_kind_string_fallback_when_not_enum(): """If something passes a raw string in ``kind`` (not a ToolCallError that would normalize it), the helper still produces a sensible value.""" class FakeError: kind = "CUSTOM_KIND" message = "x" developer_message = None status_code = None can_retry = False extra = build_tool_error_log_extra(FakeError(), tool_name="t") assert extra["error_kind"] == "CUSTOM_KIND" def test_optional_toolkit_fields_omitted_by_default(): """Optional toolkit fields are only present when supplied — Datadog can distinguish 'not set' from 'set to None'.""" extra = build_tool_error_log_extra(_err(), tool_name="MyTool") assert "toolkit_name" not in extra assert "toolkit_version" not in extra def test_optional_toolkit_fields_included_when_supplied(): extra = build_tool_error_log_extra( _err(), tool_name="MyTool", toolkit_name="MyKit", toolkit_version="1.2.3", ) assert extra["toolkit_name"] == "MyKit" assert extra["toolkit_version"] == "1.2.3" def test_additional_extras_merged(): """Worker passes ``execution_id``; MCP server may add others. The helper accepts arbitrary additions via **kwargs.""" extra = build_tool_error_log_extra( _err(), tool_name="t", execution_id="exec_42", ) assert extra["execution_id"] == "exec_42" def test_additional_extras_cannot_clobber_canonical_fields(): """A caller passing a key that collides with the canonical contract must NOT be able to override the canonical value — that would let one call site silently change the Datadog facet for every dashboard.""" extra = build_tool_error_log_extra( _err(message="canonical"), tool_name="t", error_message="OVERRIDE_ATTEMPT", tool_name_override="ignored too", ) assert extra["error_message"] == "canonical" # Non-canonical-name additions still pass through. assert extra["tool_name_override"] == "ignored too" def test_developer_message_none_propagates(): """``error_developer_message`` is present and explicitly None when the error has no developer_message — Datadog needs to distinguish 'unset' from 'set to None' on this field specifically.""" extra = build_tool_error_log_extra(_err(developer_message=None), tool_name="t") assert "error_developer_message" in extra assert extra["error_developer_message"] is None def test_span_attributes_include_developer_message_when_present(): attrs = build_tool_error_span_attributes(_err(developer_message="dev: x")) assert attrs == { "tool_error_kind": "TOOL_RUNTIME_FATAL", "tool_error_message": "Spreadsheet not found", "tool_error_developer_message": "dev: x", } def test_span_attributes_omit_empty_developer_message(): attrs = build_tool_error_span_attributes(_err(developer_message="")) assert attrs == { "tool_error_kind": "TOOL_RUNTIME_FATAL", "tool_error_message": "Spreadsheet not found", } def test_status_code_none_propagates(): extra = build_tool_error_log_extra(_err(status_code=None), tool_name="t") assert "error_status_code" in extra assert extra["error_status_code"] is None @pytest.mark.parametrize("can_retry", [True, False]) def test_can_retry_propagates_as_bool(can_retry): extra = build_tool_error_log_extra(_err(can_retry=can_retry), tool_name="t") assert extra["error_can_retry"] is can_retry