From ac41cf175795e848dd5f45657f65441ff65ee2fc Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthy Date: Sun, 12 Apr 2026 15:02:52 -0400 Subject: [PATCH 1/3] test: add logger coverage --- sdk/python/pyproject.toml | 2 +- sdk/python/tests/test_execution_logger.py | 91 ++++++++++++++++++++++- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index a4da39f2..010b71f1 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries :: Python Modules", ] -requires-python = ">=3.9,<3.14" +requires-python = ">=3.10, <3.14" dependencies = [ "fastapi", "uvicorn", diff --git a/sdk/python/tests/test_execution_logger.py b/sdk/python/tests/test_execution_logger.py index b6617c53..b9ed494e 100644 --- a/sdk/python/tests/test_execution_logger.py +++ b/sdk/python/tests/test_execution_logger.py @@ -1,5 +1,4 @@ import json - import pytest from agentfield.execution_context import ( @@ -7,7 +6,7 @@ reset_execution_context, set_execution_context, ) -from agentfield.logger import log_execution, log_info +from agentfield.logger import log_execution, log_info, AgentFieldLogger, LogLevel @pytest.mark.unit @@ -155,3 +154,91 @@ def capture(*args, **kwargs): assert captured[1]["attributes"]["duration_ms"] == 15 assert captured[1]["attributes"]["result"] == {"ok": True} assert captured[2]["attributes"]["error"] == "boom" + +@pytest.fixture +def base_logger(monkeypatch): + """ + Initializes an AgentFieldLogger with environment overrides to validate + core observability and data-handling logic. + """ + + monkeypatch.setenv("AGENTFIELD_LOG_LEVEL", "DEBUG") + monkeypatch.setenv("AGENTFIELD_LOG_PAYLOADS", "true") + monkeypatch.setenv("AGENTFIELD_LOG_TRUNCATE", "50") + monkeypatch.setenv("AGENTFIELD_LOG_TRACKING", "true") + monkeypatch.setenv("AGENTFIELD_LOG_FIRE", "true") + + logger = AgentFieldLogger(name="telemetry") + logger.logger.propagate = True + return logger + +@pytest.mark.unit +def test_logger_heartbeat_output(base_logger, caplog): + base_logger.heartbeat("Agent pulsing", status="nominal") + assert "Agent pulsing" in caplog.text + +@pytest.mark.unit +def test_logger_track_output(base_logger, caplog): + base_logger.track("token_usage", count=500) + assert "token_usage" in caplog.text + +@pytest.mark.unit +def test_logger_fire_output(base_logger, caplog): + base_logger.fire("node_transition", target="reasoning_node") + assert "node_transition" in caplog.text + +@pytest.mark.unit +def test_logger_debug_output(base_logger, caplog): + base_logger.debug("Debugging logic") + assert "Debugging" in caplog.text + +@pytest.mark.unit +def test_logger_security_output(base_logger, caplog): + base_logger.security("Sanitized keys", level="HIGH") + assert "Sanitized" in caplog.text + +@pytest.mark.unit +def test_logger_network_output(base_logger, caplog): + base_logger.network("GET https://api.openai.com", method="GET") + assert "api.openai.com" in caplog.text + +@pytest.mark.unit +def test_logger_severity_levels(base_logger, caplog): + """Tests warn, error, and critical in one pass to verify multi-line capture""" + base_logger.warn("Warning msg") + base_logger.error("Error msg") + base_logger.critical("Failure msg") + out = caplog.text + assert "Warning" in out + assert "Error" in out + assert "Failure" in out + +@pytest.mark.unit +def test_logger_success_and_setup(base_logger, caplog): + base_logger.success("Operation complete") + base_logger.setup("Environment ready") + out = caplog.text + assert "Operation" in out and "Environment" in out + +@pytest.mark.unit +def test_logger_truncation_logic(base_logger): + """Verifies AGENTFIELD_LOG_TRUNCATE env var is respected""" + long_msg = "A" * 100 + truncated = base_logger._truncate_message(long_msg) + # 50 limit + '...' suffix + assert len(truncated) == 53 + assert truncated.endswith("...") + +@pytest.mark.unit +def test_logger_payload_formatting(base_logger): + """Verifies AGENTFIELD_LOG_PAYLOADS allows raw data through""" + test_data = {"id": "123", "meta": "data"} + formatted = base_logger._format_payload(test_data) + decoded = json.loads(formatted) + assert decoded["id"] == "123" + +@pytest.mark.unit +def test_logger_short_message_handling(base_logger): + """Verifies that messages under the truncate limit are untouched""" + msg = "Short" + assert base_logger._truncate_message(msg) == "Short" \ No newline at end of file From b0a42c206d01ca35d9f613912fedbd979ee2798e Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthy Date: Sun, 12 Apr 2026 15:38:38 -0400 Subject: [PATCH 2/3] test: implement exhaustive coverage for AgentFieldLogger and modernize python requirements --- sdk/python/pyproject.toml | 1 - sdk/python/tests/test_execution_logger.py | 41 ++++++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 010b71f1..de5032ae 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -17,7 +17,6 @@ classifiers = [ "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/sdk/python/tests/test_execution_logger.py b/sdk/python/tests/test_execution_logger.py index b9ed494e..45340205 100644 --- a/sdk/python/tests/test_execution_logger.py +++ b/sdk/python/tests/test_execution_logger.py @@ -6,7 +6,7 @@ reset_execution_context, set_execution_context, ) -from agentfield.logger import log_execution, log_info, AgentFieldLogger, LogLevel +from agentfield.logger import log_execution, log_info, AgentFieldLogger @pytest.mark.unit @@ -161,7 +161,6 @@ def base_logger(monkeypatch): Initializes an AgentFieldLogger with environment overrides to validate core observability and data-handling logic. """ - monkeypatch.setenv("AGENTFIELD_LOG_LEVEL", "DEBUG") monkeypatch.setenv("AGENTFIELD_LOG_PAYLOADS", "true") monkeypatch.setenv("AGENTFIELD_LOG_TRUNCATE", "50") @@ -173,7 +172,7 @@ def base_logger(monkeypatch): return logger @pytest.mark.unit -def test_logger_heartbeat_output(base_logger, caplog): +def test_heartbeat_event(base_logger, caplog): base_logger.heartbeat("Agent pulsing", status="nominal") assert "Agent pulsing" in caplog.text @@ -204,7 +203,6 @@ def test_logger_network_output(base_logger, caplog): @pytest.mark.unit def test_logger_severity_levels(base_logger, caplog): - """Tests warn, error, and critical in one pass to verify multi-line capture""" base_logger.warn("Warning msg") base_logger.error("Error msg") base_logger.critical("Failure msg") @@ -220,25 +218,42 @@ def test_logger_success_and_setup(base_logger, caplog): out = caplog.text assert "Operation" in out and "Environment" in out +# --- FORMATTING & PAYLOAD EDGE CASES --- + @pytest.mark.unit -def test_logger_truncation_logic(base_logger): - """Verifies AGENTFIELD_LOG_TRUNCATE env var is respected""" +def test_truncate_message_at_limit(base_logger): + """Verifies message longer than truncate_length -> ends with '...'""" long_msg = "A" * 100 truncated = base_logger._truncate_message(long_msg) - # 50 limit + '...' suffix assert len(truncated) == 53 assert truncated.endswith("...") @pytest.mark.unit -def test_logger_payload_formatting(base_logger): - """Verifies AGENTFIELD_LOG_PAYLOADS allows raw data through""" +def test_logger_short_message_handling(base_logger): + """Verifies that messages under the truncate limit are untouched""" + msg = "Short" + assert base_logger._truncate_message(msg) == "Short" + +@pytest.mark.unit +def test_format_payload_hides_by_default(monkeypatch): + """Verifies dict -> '[payload hidden]' when flag is false""" + monkeypatch.setenv("AGENTFIELD_LOG_PAYLOADS", "false") + logger = AgentFieldLogger(name="privacy-test") + test_data = {"secret": "key"} + formatted = logger._format_payload(test_data) + assert formatted == "[payload hidden - set AGENTFIELD_LOG_PAYLOADS=true to show]" + +@pytest.mark.unit +def test_format_payload_shows_when_enabled(base_logger): + """Verifies AGENTFIELD_LOG_PAYLOADS=true -> JSON string""" test_data = {"id": "123", "meta": "data"} formatted = base_logger._format_payload(test_data) decoded = json.loads(formatted) assert decoded["id"] == "123" @pytest.mark.unit -def test_logger_short_message_handling(base_logger): - """Verifies that messages under the truncate limit are untouched""" - msg = "Short" - assert base_logger._truncate_message(msg) == "Short" \ No newline at end of file +def test_format_payload_handles_non_serializable(base_logger): + """Verifies fallback to str() for objects with no __dict__ or JSON support""" + test_data = {1, 2, 3} + formatted = base_logger._format_payload(test_data) + assert "{1, 2, 3}" in formatted \ No newline at end of file From e876c5a26e92486b46df2445f00dbc2c52bdc794 Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthy Date: Sun, 12 Apr 2026 16:46:45 -0400 Subject: [PATCH 3/3] Remove python 3.9 from test matrix to align with sdk requirements --- .github/workflows/sdk-python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdk-python.yml b/.github/workflows/sdk-python.yml index d45ad25a..9aa23bfe 100644 --- a/.github/workflows/sdk-python.yml +++ b/.github/workflows/sdk-python.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12'] steps: - name: Checkout uses: actions/checkout@v4