forked from HAL9000/cleveragents-core
226 lines
7.8 KiB
Python
226 lines
7.8 KiB
Python
"""Step definitions for application context analysis agent coverage."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from typing import Any, cast
|
|
from unittest.mock import patch
|
|
|
|
from behave import ( # type: ignore[import]
|
|
given as behave_given,
|
|
)
|
|
from behave import (
|
|
then as behave_then,
|
|
)
|
|
from behave import (
|
|
when as behave_when,
|
|
)
|
|
|
|
from cleveragents.application.agents.context_analysis import (
|
|
ContextAnalysisAgent,
|
|
ContextAnalysisState,
|
|
)
|
|
|
|
given = cast(Callable[..., Any], behave_given)
|
|
then = cast(Callable[..., Any], behave_then)
|
|
when = cast(Callable[..., Any], behave_when)
|
|
|
|
|
|
@when(
|
|
"I instantiate the application context agent with max files per batch {batch_size:d}"
|
|
)
|
|
def step_instantiate_application_agent(context: Any, batch_size: int) -> None:
|
|
with patch(
|
|
"cleveragents.application.agents.context_analysis.BaseAgent.__init__",
|
|
return_value=None,
|
|
) as base_init:
|
|
context.agent = ContextAnalysisAgent(max_files_per_batch=batch_size)
|
|
context.base_init_call = base_init.call_args
|
|
|
|
|
|
@then("the agent should track max files per batch of {batch_size:d}")
|
|
def step_assert_batch_size(context: Any, batch_size: int) -> None:
|
|
assert getattr(context.agent, "max_files_per_batch", None) == batch_size
|
|
|
|
|
|
@then("the base agent initializer should have been invoked with default settings")
|
|
def step_assert_base_init_called(context: Any) -> None:
|
|
call = getattr(context, "base_init_call", None)
|
|
assert call is not None, "Expected BaseAgent.__init__ to be called"
|
|
args, kwargs = call
|
|
assert args == ("openai", "gpt-4", 0.3)
|
|
assert kwargs == {}
|
|
|
|
|
|
@when("I construct the application context workflow graph")
|
|
def step_build_application_graph(context: Any) -> None:
|
|
created_graphs: list[CapturingStateGraph] = []
|
|
|
|
class CapturingStateGraph:
|
|
def __init__(self, state_cls: Any):
|
|
self.state_cls = state_cls
|
|
self.nodes: dict[str, Any] = {}
|
|
self.edges: list[tuple[Any, Any]] = []
|
|
self.entry_point: str | None = None
|
|
created_graphs.append(self)
|
|
|
|
def add_node(self, name: str, handler: Any) -> None:
|
|
self.nodes[name] = handler
|
|
|
|
def add_edge(self, start: Any, end: Any) -> None:
|
|
self.edges.append((start, end))
|
|
|
|
def set_entry_point(self, node: str) -> None:
|
|
self.entry_point = node
|
|
|
|
with (
|
|
patch(
|
|
"cleveragents.application.agents.context_analysis.StateGraph",
|
|
CapturingStateGraph,
|
|
),
|
|
patch("cleveragents.application.agents.context_analysis.END", "END"),
|
|
):
|
|
agent = ContextAnalysisAgent.__new__(ContextAnalysisAgent)
|
|
build_graph: Callable[[ContextAnalysisAgent], Any] = (
|
|
ContextAnalysisAgent._build_graph
|
|
)
|
|
context.graph = build_graph(agent) # type: ignore[misc]
|
|
|
|
context.captured_graph = created_graphs[-1]
|
|
|
|
|
|
@then("the workflow should target ContextAnalysisState as the graph state")
|
|
def step_assert_graph_state_type(context: Any) -> None:
|
|
graph = context.captured_graph
|
|
assert graph.state_cls is ContextAnalysisState
|
|
|
|
|
|
@then("the workflow should include nodes:")
|
|
def step_assert_graph_nodes(context: Any) -> None:
|
|
expected_nodes = [
|
|
line.strip() for line in context.text.strip().splitlines() if line.strip()
|
|
]
|
|
graph = context.captured_graph
|
|
assert set(expected_nodes) == set(graph.nodes.keys())
|
|
|
|
|
|
@then("the workflow should define ordered edges:")
|
|
def step_assert_graph_edges(context: Any) -> None:
|
|
lines = [line.strip() for line in context.text.strip().splitlines() if line.strip()]
|
|
expected_edges: list[tuple[str, str]] = []
|
|
for line in lines:
|
|
parts = [segment.strip() for segment in line.split("->")]
|
|
assert len(parts) == 2, f"Edge '{line}' must be formatted as 'source -> target'"
|
|
expected_edges.append((parts[0], parts[1]))
|
|
|
|
graph = context.captured_graph
|
|
actual_edges = [(str(start), str(end)) for start, end in graph.edges]
|
|
assert actual_edges == expected_edges
|
|
|
|
|
|
@given("I have a fresh application context agent state")
|
|
def step_create_fresh_state(context: Any) -> None:
|
|
state: ContextAnalysisState = {
|
|
"messages": [],
|
|
"context": {},
|
|
"result": None,
|
|
"error": None,
|
|
"metadata": {},
|
|
"files_to_analyze": [],
|
|
"analyzed_files": {},
|
|
"dependencies": {},
|
|
"project_structure": {},
|
|
"relevant_contexts": [],
|
|
}
|
|
context.state = state
|
|
|
|
|
|
@when("I run the placeholder context workflow nodes")
|
|
def step_run_placeholder_nodes(context: Any) -> None:
|
|
agent = ContextAnalysisAgent.__new__(ContextAnalysisAgent)
|
|
state: ContextAnalysisState = context.state
|
|
|
|
identify_files: Callable[[ContextAnalysisState], ContextAnalysisState] = (
|
|
agent._identify_files
|
|
)
|
|
analyze_files: Callable[[ContextAnalysisState], ContextAnalysisState] = (
|
|
agent._analyze_files
|
|
)
|
|
extract_dependencies: Callable[[ContextAnalysisState], ContextAnalysisState] = (
|
|
agent._extract_dependencies
|
|
)
|
|
build_structure: Callable[[ContextAnalysisState], ContextAnalysisState] = (
|
|
agent._build_structure
|
|
)
|
|
select_contexts: Callable[[ContextAnalysisState], ContextAnalysisState] = (
|
|
agent._select_contexts
|
|
)
|
|
finalize: Callable[[ContextAnalysisState], ContextAnalysisState] = agent._finalize
|
|
|
|
state = identify_files(state) # type: ignore[misc]
|
|
state = analyze_files(state) # type: ignore[misc]
|
|
state = extract_dependencies(state) # type: ignore[misc]
|
|
state = build_structure(state) # type: ignore[misc]
|
|
state = select_contexts(state) # type: ignore[misc]
|
|
state = finalize(state) # type: ignore[misc]
|
|
|
|
context.state = state
|
|
|
|
|
|
@then("the state should include an empty files_to_analyze list")
|
|
def step_assert_empty_files_list(context: Any) -> None:
|
|
assert context.state.get("files_to_analyze") == []
|
|
|
|
|
|
@then("the state should include an empty analyzed_files map")
|
|
def step_assert_empty_analysis(context: Any) -> None:
|
|
assert context.state.get("analyzed_files") == {}
|
|
|
|
|
|
@then("the state should include an empty dependencies map")
|
|
def step_assert_empty_dependencies(context: Any) -> None:
|
|
assert context.state.get("dependencies") == {}
|
|
|
|
|
|
@then("the state should include a project structure skeleton")
|
|
def step_assert_project_structure(context: Any) -> None:
|
|
expected_structure: dict[str, list[str]] = {
|
|
"directories": [],
|
|
"modules": [],
|
|
"entry_points": [],
|
|
}
|
|
assert context.state.get("project_structure") == expected_structure
|
|
|
|
|
|
@then("the state should include an empty relevant_contexts list")
|
|
def step_assert_empty_relevant_contexts(context: Any) -> None:
|
|
assert context.state.get("relevant_contexts") == []
|
|
|
|
|
|
@then("the state should include a finalized result summary")
|
|
def step_assert_final_result(context: Any) -> None:
|
|
result = context.state.get("result")
|
|
assert isinstance(result, dict)
|
|
typed_result = cast(dict[str, Any], result)
|
|
expected_keys = {
|
|
"analyzed_files",
|
|
"dependencies",
|
|
"project_structure",
|
|
"relevant_contexts",
|
|
"file_count",
|
|
}
|
|
key_list: list[str] = list(typed_result.keys())
|
|
result_keys: set[str] = set(key_list)
|
|
assert expected_keys.issubset(result_keys)
|
|
|
|
analyzed_files = cast(dict[str, Any], typed_result["analyzed_files"])
|
|
dependencies = cast(dict[str, Any], typed_result["dependencies"])
|
|
project_structure = cast(dict[str, Any], typed_result["project_structure"])
|
|
relevant_contexts = cast(list[Any], typed_result["relevant_contexts"])
|
|
file_count = cast(int, typed_result["file_count"])
|
|
|
|
assert dependencies == context.state.get("dependencies")
|
|
assert project_structure == context.state.get("project_structure")
|
|
assert relevant_contexts == context.state.get("relevant_contexts")
|
|
assert file_count == len(analyzed_files)
|