Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ jobs:
run: make deps
- name: Run tests
run: make test-unit
- name: Run tests with coverage
run: make test-coverage
Comment on lines +64 to +65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify test-coverage Makefile target and output
Ensure the Makefile defines a test-coverage target and that it produces a valid coverage report (e.g., coverage.xml) at the repo root. Without it, the Codecov upload will have no data.


🏁 Script executed:

#!/bin/bash
# Check Makefile for test-coverage target
rg -n "^test-coverage:" Makefile

# Confirm coverage.xml generation command exists
rg -n "coverage xml" -g "Makefile" || echo "No 'coverage xml' command found"

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Display the test-coverage target and the commands it runs
rg -n '^test-coverage:' -A 10 Makefile

Length of output: 346


Add XML coverage output for Codecov
The test-coverage target only generates HTML and terminal reports, so no coverage.xml is produced for Codecov to upload. Please update the Makefile to include XML output:

• File: Makefile (around line 48)

test-coverage:
-  	uv run pytest tests/unit_tests --cov=src --cov-report=html --cov-report=term
+  	uv run pytest tests/unit_tests \
+  		--cov=src \
+  		--cov-report=html \
+  		--cov-report=term \
+  		--cov-report=xml

This will emit coverage.xml at the repo root for the Codecov action.

🤖 Prompt for AI Agents
In .github/workflows/checks.yml at lines 64-65, the workflow runs the Makefile
target test-coverage, but the Makefile does not generate a coverage.xml file
needed for Codecov. Update the Makefile around line 48 to modify the
test-coverage target to include generating an XML coverage report (coverage.xml)
at the repo root, typically by adding a command like coverage xml or equivalent,
so Codecov can upload the coverage data correctly.

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: unittests
name: codecov-umbrella
smoke-test:
name: Smoke test
runs-on: ubuntu-latest
Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ test: test-unit
test-unit:
uv run pytest tests/unit_tests

test-coverage:
uv run pytest tests/unit_tests --cov=src --cov-report=html --cov-report=term

coverage-report:
uv run coverage html
@echo "Coverage report generated in htmlcov/index.html"

test-graphs:
uv run --env-file .env pytest -rs tests/graph_tests

Expand Down Expand Up @@ -121,6 +128,8 @@ help:
@echo 'tests - run unit tests'
@echo 'test TEST_FILE=<test_file> - run all tests in file'
@echo 'test_watch - run unit tests in watch mode'
@echo 'test-coverage - run unit tests with coverage report'
@echo 'coverage-report - generate HTML coverage report'
@echo 'ci-build-check - run build check for CI'
@echo 'demo - run demo orchestration script'

31 changes: 31 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ optional-dependencies.dev = [
"pytest~=8.3.5",
"pytest-asyncio~=0.26.0",
"pytest-dotenv~=0.5.2",
"pytest-cov>=4.1.0",
"coverage[toml]>=7.3.0",
"pytest-html>=4.0.0",
"langgraph-cli[inmem]~=0.3.1",
"openevals~=0.0.19",
"debugpy~=1.8.14",
Expand Down Expand Up @@ -100,3 +103,31 @@ ignore_errors = false

[tool.codespell]
skip = "node_modules"

[tool.coverage.run]
source = ["src"]
omit = [
"*/tests/*",
"*/test_*.py",
"*/__pycache__/*",
"src/demo/*",
]

[tool.coverage.report]
precision = 2
show_missing = true
skip_covered = false
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
Comment on lines +120 to +130
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Exclude-line pattern is incorrect – coverage filter will never match

exclude_lines contains

"if __name__ == .__main__.:",

which is missing the quoting around __main__.

-    "if __name__ == .__main__.:",
+    "if __name__ == '__main__':",

Without the fix, the typical “run as script” guard stays in the coverage report and can artificially drop overall percentages.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == '__main__':",
"if TYPE_CHECKING:",
]
🤖 Prompt for AI Agents
In pyproject.toml around lines 120 to 130, the exclude_lines pattern for the
run-as-script guard is incorrect because it lacks proper quoting around
__main__. Fix this by changing the pattern from "if __name__ == .__main__." to
"if __name__ == '__main__':" so the coverage filter correctly excludes this line
from coverage reports.


[tool.coverage.html]
directory = "htmlcov"
92 changes: 92 additions & 0 deletions tests/unit_tests/common/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
import shutil

import pytest
from langchain_core.messages import ToolMessage
from langgraph.types import Command

from common.tools.create_directory import create_directory
from common.tools.create_file import create_file
from common.tools.list_files import list_files
from common.tools.read_file import read_file
from common.tools.summarize import create_summarize_tool

# Test directory for file operations
TEST_DIR = "test_tools_dir"
Expand Down Expand Up @@ -220,3 +223,92 @@ def test_create_file_overwrite(self, setup_test_directory):
with open(test_file, "r") as f:
content = f.read()
assert content == new_content


class TestSummarize:
"""Tests for the summarize tool."""

@pytest.mark.asyncio
async def test_create_summarize_tool(self, capsys):
"""Test creating and using a summarize tool."""
agent_name = "TestAgent"
summarize_tool = create_summarize_tool(agent_name)

# Verify tool properties
assert summarize_tool.name == "summarize"
assert "summary" in summarize_tool.description.lower()

# Test invoking the tool
test_summary = "This is a test summary of the agent's work"
result = await summarize_tool.ainvoke({
"summary": test_summary,
"tool_call_id": "test_call_123"
})

# Verify the result is a Command
assert isinstance(result, Command)
assert "messages" in result.update
assert "summary" in result.update

# Verify the summary was stored correctly
assert result.update["summary"] == test_summary

# Verify the tool message
messages = result.update["messages"]
assert len(messages) == 1
assert isinstance(messages[0], ToolMessage)
assert messages[0].content == test_summary
assert messages[0].tool_call_id == "test_call_123"

# Verify console output
captured = capsys.readouterr()
assert f"======= Summary for {agent_name} =======" in captured.out
assert test_summary in captured.out
assert "==========================================" in captured.out

@pytest.mark.asyncio
async def test_summarize_tool_with_different_agents(self, capsys):
"""Test that different agents have different summarize tools."""
agent1_name = "Agent1"
agent2_name = "Agent2"

summarize1 = create_summarize_tool(agent1_name)
summarize2 = create_summarize_tool(agent2_name)

# Both should have the same tool name
assert summarize1.name == summarize2.name == "summarize"

# Test agent 1
await summarize1.ainvoke({
"summary": "Agent 1 summary",
"tool_call_id": "call_1"
})

captured1 = capsys.readouterr()
assert f"======= Summary for {agent1_name} =======" in captured1.out
assert "Agent 1 summary" in captured1.out

# Test agent 2
await summarize2.ainvoke({
"summary": "Agent 2 summary",
"tool_call_id": "call_2"
})

captured2 = capsys.readouterr()
assert f"======= Summary for {agent2_name} =======" in captured2.out
assert "Agent 2 summary" in captured2.out

@pytest.mark.asyncio
async def test_summarize_empty_summary(self):
"""Test summarize tool with empty summary."""
summarize_tool = create_summarize_tool("TestAgent")

result = await summarize_tool.ainvoke({
"summary": "",
"tool_call_id": "empty_call"
})

# Should still return a valid Command even with empty summary
assert isinstance(result, Command)
assert result.update["summary"] == ""
assert result.update["messages"][0].content == ""
Loading
Loading