Skip to content

Commit fc97b2a

Browse files
Kowserclaude
andcommitted
Migrate kitchen-sink test suite from Agentspan tests/ root
tests/test_kitchen_sink.py (25 hermetic structural/behavioral tests against examples/agents/kitchen_sink.py) sat at Agentspan's tests/ root, outside every directory the original migration copied, so it was never carried over. Placed under tests/unit/ai so the existing "Run unit tests" CI step collects it — upstream it only ever ran via bare pytest (testpaths=["tests"]) and was never a CI gate. Three changes vs upstream: - example-import sys.path adjusted for the tests/unit/ai depth (examples also moved to examples/agents in the merge) - test_credential_file_used: ToolDef.secrets no longer exists — the field is `credentials`, matching @tool(credentials=[...]) - test_analytics_has_all_advanced_features: dropped `planner is True` — planner changed from a boolean to a PLAN_EXECUTE Optional[Agent] slot and the example never sets it The last two assertions fail identically in the Agentspan repo itself — they rotted upstream precisely because no CI ever ran this file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ac0a465 commit fc97b2a

1 file changed

Lines changed: 254 additions & 0 deletions

File tree

tests/unit/ai/test_kitchen_sink.py

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# Copyright (c) 2025 Agentspan
2+
# Licensed under the MIT License. See LICENSE file in the project root for details.
3+
4+
"""Kitchen Sink test suite — structural + behavioral assertions.
5+
6+
Tests the kitchen sink structure using direct imports (no server required)
7+
and the testing framework's assertion/validation tools.
8+
"""
9+
10+
import os
11+
import sys
12+
13+
import pytest
14+
15+
# Import the example agent tree under test. Examples live at examples/agents
16+
# (repo root), three levels up from tests/unit/ai.
17+
sys.path.insert(
18+
0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "examples", "agents")
19+
)
20+
21+
from conductor.ai.agents import FinishReason, Status, Strategy
22+
from conductor.ai.agents.testing import (
23+
CorrectnessEval,
24+
EvalCase,
25+
MockEvent,
26+
assert_guardrail_passed,
27+
assert_no_errors,
28+
assert_status,
29+
assert_tool_not_used,
30+
assert_tool_used,
31+
expect,
32+
mock_run,
33+
record,
34+
replay,
35+
validate_strategy,
36+
)
37+
38+
39+
class TestKitchenSinkStructure:
40+
"""Structural tests — verify agent tree is correctly defined."""
41+
42+
def test_full_pipeline_has_all_stages(self):
43+
from kitchen_sink import full_pipeline
44+
45+
assert full_pipeline.name == "content_publishing_platform"
46+
assert len(full_pipeline.agents) == 8
47+
assert full_pipeline.strategy == Strategy.SEQUENTIAL
48+
49+
def test_intake_uses_router_strategy(self):
50+
from kitchen_sink import intake_router
51+
52+
assert intake_router.strategy == Strategy.ROUTER
53+
assert intake_router.router is not None
54+
assert intake_router.output_type is not None
55+
56+
def test_research_uses_parallel_with_scatter_gather(self):
57+
from kitchen_sink import research_coordinator, research_team
58+
59+
assert research_team.strategy == Strategy.PARALLEL
60+
assert research_coordinator.name == "research_coordinator"
61+
62+
def test_writing_pipeline_is_sequential(self):
63+
from kitchen_sink import writing_pipeline
64+
65+
assert writing_pipeline.strategy == Strategy.SEQUENTIAL
66+
67+
def test_review_has_all_guardrail_types(self):
68+
from kitchen_sink import review_agent
69+
70+
names = [g.name for g in review_agent.guardrails]
71+
assert "pii_blocker" in names # regex
72+
assert "bias_detector" in names # llm
73+
assert "fact_validator" in names # custom
74+
assert "compliance_check" in names # external
75+
76+
def test_editorial_has_hitl_tools(self):
77+
from kitchen_sink import editorial_agent
78+
79+
assert editorial_agent.strategy == Strategy.HANDOFF
80+
assert len(editorial_agent.tools) == 2 # publish_article + ask_editor
81+
82+
def test_translation_swarm_has_handoffs(self):
83+
from kitchen_sink import translation_swarm
84+
85+
assert translation_swarm.strategy == Strategy.SWARM
86+
assert len(translation_swarm.handoffs) == 3
87+
assert translation_swarm.allowed_transitions is not None
88+
89+
def test_all_eight_strategies_exercised(self):
90+
from kitchen_sink import (
91+
editorial_agent,
92+
intake_router,
93+
manual_translation,
94+
publishing_pipeline,
95+
research_team,
96+
title_brainstorm,
97+
tone_debate,
98+
translation_swarm,
99+
writing_pipeline,
100+
)
101+
102+
strategies = {
103+
intake_router.strategy,
104+
research_team.strategy,
105+
writing_pipeline.strategy,
106+
tone_debate.strategy,
107+
translation_swarm.strategy,
108+
title_brainstorm.strategy,
109+
manual_translation.strategy,
110+
publishing_pipeline.strategy,
111+
editorial_agent.strategy,
112+
}
113+
assert Strategy.ROUTER in strategies
114+
assert Strategy.PARALLEL in strategies
115+
assert Strategy.SEQUENTIAL in strategies
116+
assert Strategy.ROUND_ROBIN in strategies
117+
assert Strategy.SWARM in strategies
118+
assert Strategy.RANDOM in strategies
119+
assert Strategy.MANUAL in strategies
120+
assert Strategy.HANDOFF in strategies
121+
122+
def test_publishing_has_gate_and_termination(self):
123+
from kitchen_sink import publishing_pipeline
124+
125+
assert publishing_pipeline.termination is not None
126+
assert publishing_pipeline.gate is not None
127+
128+
def test_analytics_has_all_advanced_features(self):
129+
from kitchen_sink import analytics_agent
130+
131+
assert analytics_agent.code_execution_config is not None
132+
assert analytics_agent.cli_config is not None
133+
assert analytics_agent.thinking_budget_tokens == 2048
134+
assert analytics_agent.include_contents == "default"
135+
assert analytics_agent.required_tools is not None
136+
assert "index_article" in analytics_agent.required_tools
137+
assert analytics_agent.metadata == {"stage": "analytics", "version": "1.0"}
138+
assert analytics_agent.output_type is not None
139+
140+
def test_external_tool_is_marked(self):
141+
from conductor.ai.agents.tool import get_tool_def
142+
from kitchen_sink import external_research_aggregator
143+
144+
td = get_tool_def(external_research_aggregator)
145+
assert td.tool_type == "worker"
146+
147+
def test_external_agent_is_marked(self):
148+
from kitchen_sink import external_publisher
149+
150+
assert external_publisher.external is True
151+
152+
def test_gpt_assistant_agent_exists(self):
153+
from kitchen_sink import gpt_assistant
154+
155+
assert gpt_assistant.name == "openai_research_assistant"
156+
157+
def test_agent_tool_exists(self):
158+
from conductor.ai.agents.tool import get_tool_def
159+
from kitchen_sink import research_subtool
160+
161+
td = get_tool_def(research_subtool)
162+
assert td.name == "quick_research"
163+
164+
def test_credential_file_used(self):
165+
from conductor.ai.agents.tool import get_tool_def
166+
from kitchen_sink import research_database
167+
168+
td = get_tool_def(research_database)
169+
assert td.credentials == ["RESEARCH_API_KEY"]
170+
171+
172+
class TestKitchenSinkHelpers:
173+
"""Test helper functions."""
174+
175+
def test_contains_pii_ssn(self):
176+
from kitchen_sink_helpers import contains_pii
177+
178+
assert contains_pii("My SSN is 123-45-6789")
179+
assert not contains_pii("No PII here")
180+
181+
def test_contains_pii_credit_card(self):
182+
from kitchen_sink_helpers import contains_pii
183+
184+
assert contains_pii("Card: 4532-0150-1234-5678")
185+
assert contains_pii("Card: 4532015012345678")
186+
187+
def test_contains_sql_injection(self):
188+
from kitchen_sink_helpers import contains_sql_injection
189+
190+
assert contains_sql_injection("DROP TABLE users")
191+
assert contains_sql_injection("' OR '1'='1'")
192+
assert not contains_sql_injection("normal search query")
193+
194+
def test_classification_result_model(self):
195+
from kitchen_sink_helpers import ClassificationResult
196+
197+
result = ClassificationResult(
198+
category="tech", priority=1, tags=["quantum"], metadata={}
199+
)
200+
assert result.category == "tech"
201+
assert result.priority == 1
202+
203+
def test_article_report_model(self):
204+
from kitchen_sink_helpers import ArticleReport
205+
206+
report = ArticleReport(
207+
word_count=1500,
208+
sentiment_score=0.8,
209+
readability_grade="B+",
210+
top_keywords=["quantum", "computing"],
211+
)
212+
assert report.word_count == 1500
213+
214+
def test_callback_log(self):
215+
from kitchen_sink_helpers import callback_log
216+
217+
callback_log.clear()
218+
callback_log.log("test_event", key="value")
219+
assert len(callback_log.events) == 1
220+
assert callback_log.events[0]["type"] == "test_event"
221+
assert callback_log.events[0]["key"] == "value"
222+
callback_log.clear()
223+
assert len(callback_log.events) == 0
224+
225+
226+
class TestStrategyValidation:
227+
"""Validate strategy configuration (feature #82)."""
228+
229+
def test_validate_parallel_strategy(self):
230+
from kitchen_sink import research_team
231+
232+
assert research_team.strategy == Strategy.PARALLEL
233+
234+
def test_validate_sequential_strategy(self):
235+
from kitchen_sink import writing_pipeline
236+
237+
assert writing_pipeline.strategy == Strategy.SEQUENTIAL
238+
239+
def test_validate_swarm_strategy(self):
240+
from kitchen_sink import translation_swarm
241+
242+
assert translation_swarm.strategy == Strategy.SWARM
243+
244+
245+
class TestEvalRunner:
246+
"""Correctness evaluation framework (feature #83)."""
247+
248+
def test_eval_case_definition(self):
249+
eval_case = EvalCase(
250+
name="kitchen_sink_basic",
251+
prompt="Write a tech article about quantum computing",
252+
expect_output_contains=["quantum", "computing"],
253+
)
254+
assert eval_case.name == "kitchen_sink_basic"

0 commit comments

Comments
 (0)