"""F11 Bootstrap 拼装单元测试 按 test-plan-v2.6.md §F11: - T1: 各 role 拼装(P0) - T2: token 估算(P0) - T3: 缺失组件降级(P1) - T4: 模板变量替换(P1) """ import pytest from pathlib import Path from src.blackboard.models import Task from src.daemon.bootstrap import BootstrapBuilder, estimate_tokens @pytest.fixture def builder(): return BootstrapBuilder(max_tokens=4096) @pytest.fixture def builder_with_templates(tmp_path): template_dir = tmp_path / "templates" template_dir.mkdir() (template_dir / "executor.md").write_text("# Executor Role\nYou execute tasks.") (template_dir / "reviewer.md").write_text("# Reviewer Role\nYou review code.") (template_dir / "planner.md").write_text("# Planner Role\nYou plan tasks.") return BootstrapBuilder(template_dir=template_dir, max_tokens=4096) # --------------------------------------------------------------------------- # T1: 各 role 拼装 # --------------------------------------------------------------------------- class TestRoleBootstrap: def test_executor_bootstrap(self, builder): b = builder.build( role="executor", task_context={"task_id": "t1", "title": "Write tests"}, ) assert "Write tests" in b assert "t1" in b def test_reviewer_bootstrap(self, builder): b = builder.build( role="reviewer", task_context={"task_id": "t2", "title": "Review PR"}, ) assert "Review PR" in b def test_planner_bootstrap(self, builder): b = builder.build( role="planner", task_context={"task_id": "t3", "title": "Plan sprint"}, ) assert "Plan sprint" in b def test_executor_with_guardrail(self, builder): b = builder.build( role="executor", guardrail_rules="## Guardrail\nNo dangerous ops", ) assert "Guardrail" in b def test_reviewer_no_guardrail(self, builder): b = builder.build( role="reviewer", guardrail_rules="## Guardrail\nNo dangerous ops", ) assert "Guardrail" not in b # reviewer 不注入 guardrail def test_executor_with_review_protocol(self, builder): b = builder.build( role="executor", review_protocols="## Review Protocol\nCheck tests", ) assert "Review Protocol" in b def test_reviewer_with_review_protocol(self, builder): b = builder.build( role="reviewer", review_protocols="## Review Protocol\nCheck quality", ) assert "Review Protocol" in b def test_with_template(self, builder_with_templates): b = builder_with_templates.build(role="executor") assert "Executor Role" in b assert "You execute tasks" in b # --------------------------------------------------------------------------- # T2: token 估算 # --------------------------------------------------------------------------- class TestTokenEstimation: def test_estimate_tokens_basic(self): assert estimate_tokens("hello") > 0 def test_estimate_tokens_long_text(self): text = "a" * 1000 tokens = estimate_tokens(text) assert 200 < tokens < 400 # ~333 def test_bootstrap_under_limit(self, builder): b = builder.build( role="executor", task_context={"title": "Short task"}, ) assert estimate_tokens(b) <= 4096 def test_bootstrap_over_limit_truncates(self): builder = BootstrapBuilder(max_tokens=10) b = builder.build( role="executor", task_context={"title": "A" * 10000}, ) assert estimate_tokens(b) <= 20 # 接近限制 assert "truncated" in b # --------------------------------------------------------------------------- # T3: 缺失组件降级 # --------------------------------------------------------------------------- class TestGracefulDegradation: def test_no_task_context(self, builder): b = builder.build(role="executor") assert b # 不为空 def test_no_project_context(self, builder): b = builder.build( role="executor", task_context={"title": "Task"}, ) assert "Task" in b def test_no_template(self, builder): b = builder.build(role="executor") assert b # 没有 template 也不崩溃 def test_empty_experiences(self, builder): b = builder.build(role="executor", experiences=[]) assert b def test_template_dir_not_exists(self, tmp_path): builder = BootstrapBuilder(template_dir=tmp_path / "nonexistent") b = builder.build(role="executor") assert b # 不崩溃 # --------------------------------------------------------------------------- # T4: 便捷方法 + 项目上下文 # --------------------------------------------------------------------------- class TestBuildForTask: def test_build_for_task_object(self, builder): task = Task( id="t1", title="Build Feature", status="pending", assigned_by="daemon", task_type="coding", description="Implement X with tests", must_haves="Unit tests, Documentation", risk_level="high", ) b = builder.build_for_task(task, role="executor") assert "Build Feature" in b assert "Implement X" in b assert "high" in b def test_build_for_task_with_project(self, builder): task = Task(id="t1", title="T", status="pending", assigned_by="d") b = builder.build_for_task( task, role="executor", project_config={"name": "My Project", "agents": ["a1", "a2"]}, ) assert "My Project" in b assert "a1" in b def test_with_experiences(self, builder): task = Task(id="t1", title="T", status="pending", assigned_by="d") experiences = [ {"category": "pitfall", "summary": "Always test edge cases"}, {"category": "best_practice", "summary": "Use type hints"}, ] b = builder.build_for_task(task, role="executor", experiences=experiences) assert "pitfall" in b assert "Always test edge cases" in b def test_with_skills(self, builder): skills = [ {"name": "code-review", "description": "Review code quality"}, ] b = builder.build( role="executor", skill_descriptions=skills, ) assert "code-review" in b assert "Review code quality" in b def test_with_depends_on_outputs(self, builder): b = builder.build( role="executor", task_context={ "title": "T", "depends_on_outputs": [ {"task_id": "t0", "summary": "Data downloaded"}, ], }, ) assert "前序产出" in b assert "Data downloaded" in b