PlanOpticon

planopticon / tests / test_agent_skills.py
Source Blame History 627 lines
0981a08… noreply 1 """Tests for agent skill execute() methods with mocked context."""
0981a08… noreply 2
0981a08… noreply 3 import json
0981a08… noreply 4 from dataclasses import dataclass
0981a08… noreply 5 from unittest.mock import MagicMock
0981a08… noreply 6
0981a08… noreply 7 import pytest
0981a08… noreply 8
0981a08… noreply 9 from video_processor.agent.skills.base import (
0981a08… noreply 10 AgentContext,
0981a08… noreply 11 Artifact,
0981a08… noreply 12 _skills,
0981a08… noreply 13 )
0981a08… noreply 14
0981a08… noreply 15 # ---------------------------------------------------------------------------
0981a08… noreply 16 # Fixtures
0981a08… noreply 17 # ---------------------------------------------------------------------------
0981a08… noreply 18
0981a08… noreply 19
0981a08… noreply 20 @pytest.fixture(autouse=True)
0981a08… noreply 21 def _clean_skill_registry():
0981a08… noreply 22 """Save and restore the global skill registry between tests."""
0981a08… noreply 23 original = dict(_skills)
0981a08… noreply 24 yield
0981a08… noreply 25 _skills.clear()
0981a08… noreply 26 _skills.update(original)
0981a08… noreply 27
0981a08… noreply 28
0981a08… noreply 29 @dataclass
0981a08… noreply 30 class FakeEntity:
0981a08… noreply 31 name: str
0981a08… noreply 32 type: str
0981a08… noreply 33
0981a08… noreply 34 def __str__(self):
0981a08… noreply 35 return self.name
0981a08… noreply 36
0981a08… noreply 37
0981a08… noreply 38 class FakeQueryResult:
0981a08… noreply 39 """Mimics QueryResult.to_text()."""
0981a08… noreply 40
0981a08… noreply 41 def __init__(self, text="Stats: 10 entities, 5 relationships"):
0981a08… noreply 42 self._text = text
0981a08… noreply 43
0981a08… noreply 44 def to_text(self):
0981a08… noreply 45 return self._text
0981a08… noreply 46
0981a08… noreply 47
0981a08… noreply 48 def _make_context(
0981a08… noreply 49 chat_response="# Generated Content\n\nSome markdown content.",
0981a08… noreply 50 planning_entities=None,
0981a08… noreply 51 ):
0981a08… noreply 52 """Build an AgentContext with mocked query_engine and provider_manager."""
0981a08… noreply 53 ctx = AgentContext()
0981a08… noreply 54
0981a08… noreply 55 qe = MagicMock()
0981a08… noreply 56 qe.stats.return_value = FakeQueryResult("Stats: 10 entities, 5 rels")
0981a08… noreply 57 qe.entities.return_value = FakeQueryResult("Entity1, Entity2")
0981a08… noreply 58 qe.relationships.return_value = FakeQueryResult("Entity1 -> Entity2")
0981a08… noreply 59 ctx.query_engine = qe
0981a08… noreply 60
0981a08… noreply 61 pm = MagicMock()
0981a08… noreply 62 pm.chat.return_value = chat_response
0981a08… noreply 63 ctx.provider_manager = pm
0981a08… noreply 64
0981a08… noreply 65 ctx.knowledge_graph = MagicMock()
0981a08… noreply 66
0981a08… noreply 67 if planning_entities is not None:
0981a08… noreply 68 ctx.planning_entities = planning_entities
0981a08… noreply 69 else:
0981a08… noreply 70 ctx.planning_entities = [
0981a08… noreply 71 FakeEntity(name="Auth system", type="feature"),
0981a08… noreply 72 FakeEntity(name="Launch v1", type="milestone"),
0981a08… noreply 73 FakeEntity(name="Must be fast", type="constraint"),
0981a08… noreply 74 FakeEntity(name="Build dashboard", type="goal"),
0981a08… noreply 75 FakeEntity(name="API depends on auth", type="dependency"),
0981a08… noreply 76 FakeEntity(name="User login", type="requirement"),
0981a08… noreply 77 ]
0981a08… noreply 78
0981a08… noreply 79 return ctx
0981a08… noreply 80
0981a08… noreply 81
0981a08… noreply 82 # ---------------------------------------------------------------------------
0981a08… noreply 83 # ProjectPlanSkill
0981a08… noreply 84 # ---------------------------------------------------------------------------
0981a08… noreply 85
0981a08… noreply 86
0981a08… noreply 87 class TestProjectPlanSkill:
0981a08… noreply 88 def test_execute_returns_artifact(self):
0981a08… noreply 89 from video_processor.agent.skills.project_plan import ProjectPlanSkill
0981a08… noreply 90
0981a08… noreply 91 skill = ProjectPlanSkill()
0981a08… noreply 92 ctx = _make_context()
0981a08… noreply 93 artifact = skill.execute(ctx)
0981a08… noreply 94
0981a08… noreply 95 assert isinstance(artifact, Artifact)
0981a08… noreply 96 assert artifact.artifact_type == "project_plan"
0981a08… noreply 97 assert artifact.format == "markdown"
0981a08… noreply 98 assert len(artifact.content) > 0
0981a08… noreply 99
0981a08… noreply 100 def test_execute_calls_provider(self):
0981a08… noreply 101 from video_processor.agent.skills.project_plan import ProjectPlanSkill
0981a08… noreply 102
0981a08… noreply 103 skill = ProjectPlanSkill()
0981a08… noreply 104 ctx = _make_context()
0981a08… noreply 105 skill.execute(ctx)
0981a08… noreply 106
0981a08… noreply 107 ctx.provider_manager.chat.assert_called_once()
0981a08… noreply 108 call_args = ctx.provider_manager.chat.call_args
0981a08… noreply 109 messages = call_args[1]["messages"] if "messages" in call_args[1] else call_args[0][0]
0981a08… noreply 110 assert len(messages) == 1
0981a08… noreply 111 assert messages[0]["role"] == "user"
0981a08… noreply 112
0981a08… noreply 113 def test_execute_queries_graph(self):
0981a08… noreply 114 from video_processor.agent.skills.project_plan import ProjectPlanSkill
0981a08… noreply 115
0981a08… noreply 116 skill = ProjectPlanSkill()
0981a08… noreply 117 ctx = _make_context()
0981a08… noreply 118 skill.execute(ctx)
0981a08… noreply 119
0981a08… noreply 120 ctx.query_engine.stats.assert_called_once()
0981a08… noreply 121 ctx.query_engine.entities.assert_called_once()
0981a08… noreply 122 ctx.query_engine.relationships.assert_called_once()
0981a08… noreply 123
0981a08… noreply 124
0981a08… noreply 125 # ---------------------------------------------------------------------------
0981a08… noreply 126 # PRDSkill
0981a08… noreply 127 # ---------------------------------------------------------------------------
0981a08… noreply 128
0981a08… noreply 129
0981a08… noreply 130 class TestPRDSkill:
0981a08… noreply 131 def test_execute_returns_artifact(self):
0981a08… noreply 132 from video_processor.agent.skills.prd import PRDSkill
0981a08… noreply 133
0981a08… noreply 134 skill = PRDSkill()
0981a08… noreply 135 ctx = _make_context()
0981a08… noreply 136 artifact = skill.execute(ctx)
0981a08… noreply 137
0981a08… noreply 138 assert isinstance(artifact, Artifact)
0981a08… noreply 139 assert artifact.artifact_type == "prd"
0981a08… noreply 140 assert artifact.format == "markdown"
0981a08… noreply 141
0981a08… noreply 142 def test_execute_filters_relevant_entities(self):
0981a08… noreply 143 from video_processor.agent.skills.prd import PRDSkill
0981a08… noreply 144
0981a08… noreply 145 skill = PRDSkill()
0981a08… noreply 146 ctx = _make_context()
0981a08… noreply 147 skill.execute(ctx)
0981a08… noreply 148
0981a08… noreply 149 # Should still call provider
0981a08… noreply 150 ctx.provider_manager.chat.assert_called_once()
0981a08… noreply 151
0981a08… noreply 152 def test_execute_with_no_relevant_entities(self):
0981a08… noreply 153 from video_processor.agent.skills.prd import PRDSkill
0981a08… noreply 154
0981a08… noreply 155 skill = PRDSkill()
0981a08… noreply 156 ctx = _make_context(
0981a08… noreply 157 planning_entities=[
0981a08… noreply 158 FakeEntity(name="Some goal", type="goal"),
0981a08… noreply 159 ]
0981a08… noreply 160 )
0981a08… noreply 161 artifact = skill.execute(ctx)
0981a08… noreply 162
0981a08… noreply 163 assert isinstance(artifact, Artifact)
0981a08… noreply 164 assert artifact.artifact_type == "prd"
0981a08… noreply 165
0981a08… noreply 166
0981a08… noreply 167 # ---------------------------------------------------------------------------
0981a08… noreply 168 # RoadmapSkill
0981a08… noreply 169 # ---------------------------------------------------------------------------
0981a08… noreply 170
0981a08… noreply 171
0981a08… noreply 172 class TestRoadmapSkill:
0981a08… noreply 173 def test_execute_returns_artifact(self):
0981a08… noreply 174 from video_processor.agent.skills.roadmap import RoadmapSkill
0981a08… noreply 175
0981a08… noreply 176 skill = RoadmapSkill()
0981a08… noreply 177 ctx = _make_context()
0981a08… noreply 178 artifact = skill.execute(ctx)
0981a08… noreply 179
0981a08… noreply 180 assert isinstance(artifact, Artifact)
0981a08… noreply 181 assert artifact.artifact_type == "roadmap"
0981a08… noreply 182 assert artifact.format == "markdown"
0981a08… noreply 183
0981a08… noreply 184 def test_execute_with_no_relevant_entities(self):
0981a08… noreply 185 from video_processor.agent.skills.roadmap import RoadmapSkill
0981a08… noreply 186
0981a08… noreply 187 skill = RoadmapSkill()
0981a08… noreply 188 ctx = _make_context(
0981a08… noreply 189 planning_entities=[
0981a08… noreply 190 FakeEntity(name="Some constraint", type="constraint"),
0981a08… noreply 191 ]
0981a08… noreply 192 )
0981a08… noreply 193 artifact = skill.execute(ctx)
0981a08… noreply 194
0981a08… noreply 195 assert isinstance(artifact, Artifact)
0981a08… noreply 196
0981a08… noreply 197
0981a08… noreply 198 # ---------------------------------------------------------------------------
0981a08… noreply 199 # TaskBreakdownSkill
0981a08… noreply 200 # ---------------------------------------------------------------------------
0981a08… noreply 201
0981a08… noreply 202
0981a08… noreply 203 class TestTaskBreakdownSkill:
0981a08… noreply 204 def test_execute_returns_artifact_json(self):
0981a08… noreply 205 from video_processor.agent.skills.task_breakdown import TaskBreakdownSkill
0981a08… noreply 206
0981a08… noreply 207 tasks_json = json.dumps(
0981a08… noreply 208 [
0981a08… noreply 209 {
0981a08… noreply 210 "id": "T1",
0981a08… noreply 211 "title": "Setup",
0981a08… noreply 212 "description": "Init",
0981a08… noreply 213 "depends_on": [],
0981a08… noreply 214 "priority": "high",
0981a08… noreply 215 "estimate": "1d",
0981a08… noreply 216 "assignee_role": "dev",
0981a08… noreply 217 },
0981a08… noreply 218 ]
0981a08… noreply 219 )
0981a08… noreply 220 skill = TaskBreakdownSkill()
0981a08… noreply 221 ctx = _make_context(chat_response=tasks_json)
0981a08… noreply 222 artifact = skill.execute(ctx)
0981a08… noreply 223
0981a08… noreply 224 assert isinstance(artifact, Artifact)
0981a08… noreply 225 assert artifact.artifact_type == "task_list"
0981a08… noreply 226 assert artifact.format == "json"
0981a08… noreply 227 assert "tasks" in artifact.metadata
0981a08… noreply 228 assert len(artifact.metadata["tasks"]) == 1
0981a08… noreply 229
0981a08… noreply 230 def test_execute_with_non_json_response(self):
0981a08… noreply 231 from video_processor.agent.skills.task_breakdown import TaskBreakdownSkill
0981a08… noreply 232
0981a08… noreply 233 skill = TaskBreakdownSkill()
0981a08… noreply 234 ctx = _make_context(chat_response="Not valid JSON at all")
0981a08… noreply 235 artifact = skill.execute(ctx)
0981a08… noreply 236
0981a08… noreply 237 assert isinstance(artifact, Artifact)
0981a08… noreply 238 assert artifact.artifact_type == "task_list"
0981a08… noreply 239
0981a08… noreply 240 def test_execute_with_no_relevant_entities(self):
0981a08… noreply 241 from video_processor.agent.skills.task_breakdown import TaskBreakdownSkill
0981a08… noreply 242
0981a08… noreply 243 tasks_json = json.dumps([])
0981a08… noreply 244 skill = TaskBreakdownSkill()
0981a08… noreply 245 ctx = _make_context(
0981a08… noreply 246 chat_response=tasks_json,
0981a08… noreply 247 planning_entities=[FakeEntity(name="X", type="constraint")],
0981a08… noreply 248 )
0981a08… noreply 249 artifact = skill.execute(ctx)
0981a08… noreply 250 assert artifact.metadata["tasks"] == []
0981a08… noreply 251
0981a08… noreply 252
0981a08… noreply 253 # ---------------------------------------------------------------------------
0981a08… noreply 254 # DocGeneratorSkill
0981a08… noreply 255 # ---------------------------------------------------------------------------
0981a08… noreply 256
0981a08… noreply 257
0981a08… noreply 258 class TestDocGeneratorSkill:
0981a08… noreply 259 def test_execute_default_type(self):
0981a08… noreply 260 from video_processor.agent.skills.doc_generator import DocGeneratorSkill
0981a08… noreply 261
0981a08… noreply 262 skill = DocGeneratorSkill()
0981a08… noreply 263 ctx = _make_context()
0981a08… noreply 264 artifact = skill.execute(ctx)
0981a08… noreply 265
0981a08… noreply 266 assert isinstance(artifact, Artifact)
0981a08… noreply 267 assert artifact.artifact_type == "document"
0981a08… noreply 268 assert artifact.format == "markdown"
0981a08… noreply 269 assert artifact.metadata["doc_type"] == "technical_doc"
0981a08… noreply 270
0981a08… noreply 271 def test_execute_adr_type(self):
0981a08… noreply 272 from video_processor.agent.skills.doc_generator import DocGeneratorSkill
0981a08… noreply 273
0981a08… noreply 274 skill = DocGeneratorSkill()
0981a08… noreply 275 ctx = _make_context()
0981a08… noreply 276 artifact = skill.execute(ctx, doc_type="adr")
0981a08… noreply 277
0981a08… noreply 278 assert artifact.metadata["doc_type"] == "adr"
0981a08… noreply 279
0981a08… noreply 280 def test_execute_meeting_notes_type(self):
0981a08… noreply 281 from video_processor.agent.skills.doc_generator import DocGeneratorSkill
0981a08… noreply 282
0981a08… noreply 283 skill = DocGeneratorSkill()
0981a08… noreply 284 ctx = _make_context()
0981a08… noreply 285 artifact = skill.execute(ctx, doc_type="meeting_notes")
0981a08… noreply 286
0981a08… noreply 287 assert artifact.metadata["doc_type"] == "meeting_notes"
0981a08… noreply 288
0981a08… noreply 289 def test_execute_unknown_type_falls_back(self):
0981a08… noreply 290 from video_processor.agent.skills.doc_generator import DocGeneratorSkill
0981a08… noreply 291
0981a08… noreply 292 skill = DocGeneratorSkill()
0981a08… noreply 293 ctx = _make_context()
0981a08… noreply 294 artifact = skill.execute(ctx, doc_type="unknown_type")
0981a08… noreply 295
0981a08… noreply 296 # Falls back to technical_doc prompt
0981a08… noreply 297 assert artifact.artifact_type == "document"
0981a08… noreply 298
0981a08… noreply 299
0981a08… noreply 300 # ---------------------------------------------------------------------------
0981a08… noreply 301 # RequirementsChatSkill
0981a08… noreply 302 # ---------------------------------------------------------------------------
0981a08… noreply 303
0981a08… noreply 304
0981a08… noreply 305 class TestRequirementsChatSkill:
0981a08… noreply 306 def test_execute_returns_artifact(self):
0981a08… noreply 307 from video_processor.agent.skills.requirements_chat import RequirementsChatSkill
0981a08… noreply 308
0981a08… noreply 309 questions = {
0981a08… noreply 310 "questions": [
0981a08… noreply 311 {"id": "Q1", "category": "goals", "question": "What?", "context": "Why"},
0981a08… noreply 312 ]
0981a08… noreply 313 }
0981a08… noreply 314 skill = RequirementsChatSkill()
0981a08… noreply 315 ctx = _make_context(chat_response=json.dumps(questions))
0981a08… noreply 316 artifact = skill.execute(ctx)
0981a08… noreply 317
0981a08… noreply 318 assert isinstance(artifact, Artifact)
0981a08… noreply 319 assert artifact.artifact_type == "requirements"
0981a08… noreply 320 assert artifact.format == "json"
0981a08… noreply 321 assert artifact.metadata["stage"] == "questionnaire"
0981a08… noreply 322
0981a08… noreply 323 def test_gather_requirements(self):
0981a08… noreply 324 from video_processor.agent.skills.requirements_chat import RequirementsChatSkill
0981a08… noreply 325
0981a08… noreply 326 reqs = {
0981a08… noreply 327 "goals": ["Build auth"],
0981a08… noreply 328 "constraints": ["Budget < 10k"],
0981a08… noreply 329 "priorities": ["Security"],
0981a08… noreply 330 "scope": {"in_scope": ["Login"], "out_of_scope": ["SSO"]},
0981a08… noreply 331 }
0981a08… noreply 332 skill = RequirementsChatSkill()
0981a08… noreply 333 ctx = _make_context(chat_response=json.dumps(reqs))
0981a08… noreply 334 result = skill.gather_requirements(ctx, {"Q1": "We need auth", "Q2": "Budget is limited"})
0981a08… noreply 335
0981a08… noreply 336 assert isinstance(result, dict)
0981a08… noreply 337
0981a08… noreply 338 def test_gather_requirements_non_json_response(self):
0981a08… noreply 339 from video_processor.agent.skills.requirements_chat import RequirementsChatSkill
0981a08… noreply 340
0981a08… noreply 341 skill = RequirementsChatSkill()
0981a08… noreply 342 ctx = _make_context(chat_response="Not JSON")
0981a08… noreply 343 result = skill.gather_requirements(ctx, {"Q1": "answer"})
0981a08… noreply 344
0981a08… noreply 345 assert isinstance(result, dict)
0981a08… noreply 346
0981a08… noreply 347
0981a08… noreply 348 # ---------------------------------------------------------------------------
0981a08… noreply 349 # Skill metadata
0981a08… noreply 350 # ---------------------------------------------------------------------------
0981a08… noreply 351
0981a08… noreply 352
0981a08… noreply 353 class TestSkillMetadata:
0981a08… noreply 354 def test_project_plan_name(self):
0981a08… noreply 355 from video_processor.agent.skills.project_plan import ProjectPlanSkill
0981a08… noreply 356
0981a08… noreply 357 assert ProjectPlanSkill.name == "project_plan"
0981a08… noreply 358
0981a08… noreply 359 def test_prd_name(self):
0981a08… noreply 360 from video_processor.agent.skills.prd import PRDSkill
0981a08… noreply 361
0981a08… noreply 362 assert PRDSkill.name == "prd"
0981a08… noreply 363
0981a08… noreply 364 def test_roadmap_name(self):
0981a08… noreply 365 from video_processor.agent.skills.roadmap import RoadmapSkill
0981a08… noreply 366
0981a08… noreply 367 assert RoadmapSkill.name == "roadmap"
0981a08… noreply 368
0981a08… noreply 369 def test_task_breakdown_name(self):
0981a08… noreply 370 from video_processor.agent.skills.task_breakdown import TaskBreakdownSkill
0981a08… noreply 371
0981a08… noreply 372 assert TaskBreakdownSkill.name == "task_breakdown"
0981a08… noreply 373
0981a08… noreply 374 def test_doc_generator_name(self):
0981a08… noreply 375 from video_processor.agent.skills.doc_generator import DocGeneratorSkill
0981a08… noreply 376
0981a08… noreply 377 assert DocGeneratorSkill.name == "doc_generator"
0981a08… noreply 378
0981a08… noreply 379 def test_requirements_chat_name(self):
0981a08… noreply 380 from video_processor.agent.skills.requirements_chat import RequirementsChatSkill
0981a08… noreply 381
0981a08… noreply 382 assert RequirementsChatSkill.name == "requirements_chat"
0981a08… noreply 383
0981a08… noreply 384 def test_can_execute_with_context(self):
0981a08… noreply 385 from video_processor.agent.skills.project_plan import ProjectPlanSkill
0981a08… noreply 386
0981a08… noreply 387 skill = ProjectPlanSkill()
0981a08… noreply 388 ctx = _make_context()
0981a08… noreply 389 assert skill.can_execute(ctx) is True
0981a08… noreply 390
0981a08… noreply 391 def test_can_execute_without_kg(self):
0981a08… noreply 392 from video_processor.agent.skills.project_plan import ProjectPlanSkill
0981a08… noreply 393
0981a08… noreply 394 skill = ProjectPlanSkill()
0981a08… noreply 395 ctx = _make_context()
0981a08… noreply 396 ctx.knowledge_graph = None
0981a08… noreply 397 assert skill.can_execute(ctx) is False
0981a08… noreply 398
0981a08… noreply 399 def test_can_execute_without_provider(self):
0981a08… noreply 400 from video_processor.agent.skills.project_plan import ProjectPlanSkill
0981a08… noreply 401
0981a08… noreply 402 skill = ProjectPlanSkill()
0981a08… noreply 403 ctx = _make_context()
0981a08… noreply 404 ctx.provider_manager = None
0981a08… noreply 405 assert skill.can_execute(ctx) is False
0981a08… noreply 406
0981a08… noreply 407
0981a08… noreply 408 # ---------------------------------------------------------------------------
0981a08… noreply 409 # WikiGeneratorSkill
0981a08… noreply 410 # ---------------------------------------------------------------------------
0981a08… noreply 411
0981a08… noreply 412
0981a08… noreply 413 class TestWikiGeneratorSkill:
0981a08… noreply 414 def _sample_kg_data(self):
0981a08… noreply 415 return {
0981a08… noreply 416 "nodes": [
0981a08… noreply 417 {
0981a08… noreply 418 "name": "Python",
0981a08… noreply 419 "type": "technology",
0981a08… noreply 420 "descriptions": ["A programming language"],
0981a08… noreply 421 },
0981a08… noreply 422 {
0981a08… noreply 423 "name": "Alice",
0981a08… noreply 424 "type": "person",
0981a08… noreply 425 "descriptions": ["Lead developer"],
0981a08… noreply 426 },
0981a08… noreply 427 {
0981a08… noreply 428 "name": "FastAPI",
0981a08… noreply 429 "type": "technology",
0981a08… noreply 430 "descriptions": ["Web framework"],
0981a08… noreply 431 },
0981a08… noreply 432 ],
0981a08… noreply 433 "relationships": [
0981a08… noreply 434 {"source": "Alice", "target": "Python", "type": "uses"},
0981a08… noreply 435 {"source": "FastAPI", "target": "Python", "type": "built_with"},
0981a08… noreply 436 ],
0981a08… noreply 437 }
0981a08… noreply 438
0981a08… noreply 439 def test_generate_wiki(self):
0981a08… noreply 440 from video_processor.agent.skills.wiki_generator import generate_wiki
0981a08… noreply 441
0981a08… noreply 442 pages = generate_wiki(self._sample_kg_data(), title="Test Wiki")
0981a08… noreply 443
0981a08… noreply 444 assert "Home" in pages
0981a08… noreply 445 assert "_Sidebar" in pages
0981a08… noreply 446 assert "Test Wiki" in pages["Home"]
0981a08… noreply 447 assert "3" in pages["Home"] # 3 entities
0981a08… noreply 448 assert "2" in pages["Home"] # 2 relationships
0981a08… noreply 449
0981a08… noreply 450 # Entity pages should exist
0981a08… noreply 451 assert "Python" in pages
0981a08… noreply 452 assert "Alice" in pages
0981a08… noreply 453 assert "FastAPI" in pages
0981a08… noreply 454
0981a08… noreply 455 # Type index pages should exist
0981a08… noreply 456 assert "Technology" in pages
0981a08… noreply 457 assert "Person" in pages
0981a08… noreply 458
0981a08… noreply 459 # Alice's page should reference Python
0981a08… noreply 460 assert "Python" in pages["Alice"]
0981a08… noreply 461 assert "uses" in pages["Alice"]
0981a08… noreply 462
0981a08… noreply 463 def test_generate_wiki_with_artifacts(self):
0981a08… noreply 464 from video_processor.agent.skills.wiki_generator import generate_wiki
0981a08… noreply 465
0981a08… noreply 466 art = Artifact(
0981a08… noreply 467 name="Project Plan",
0981a08… noreply 468 content="# Plan\n\nDo the thing.",
0981a08… noreply 469 artifact_type="project_plan",
0981a08… noreply 470 format="markdown",
0981a08… noreply 471 )
0981a08… noreply 472 pages = generate_wiki(self._sample_kg_data(), artifacts=[art])
0981a08… noreply 473
0981a08… noreply 474 assert "Project-Plan" in pages
0981a08… noreply 475 assert "Do the thing." in pages["Project-Plan"]
0981a08… noreply 476 assert "Planning Artifacts" in pages["Home"]
0981a08… noreply 477
0981a08… noreply 478 def test_write_wiki(self, tmp_path):
0981a08… noreply 479 from video_processor.agent.skills.wiki_generator import write_wiki
0981a08… noreply 480
0981a08… noreply 481 pages = {
0981a08… noreply 482 "Home": "# Home\n\nWelcome.",
0981a08… noreply 483 "Page-One": "# Page One\n\nContent.",
0981a08… noreply 484 }
0981a08… noreply 485 paths = write_wiki(pages, tmp_path / "wiki")
0981a08… noreply 486
0981a08… noreply 487 assert len(paths) == 2
0981a08… noreply 488 assert (tmp_path / "wiki" / "Home.md").exists()
0981a08… noreply 489 assert (tmp_path / "wiki" / "Page-One.md").exists()
0981a08… noreply 490 assert "Welcome." in (tmp_path / "wiki" / "Home.md").read_text()
0981a08… noreply 491
0981a08… noreply 492 def test_sanitize_filename(self):
0981a08… noreply 493 from video_processor.agent.skills.wiki_generator import _sanitize_filename
0981a08… noreply 494
0981a08… noreply 495 assert _sanitize_filename("Hello World") == "Hello-World"
0981a08… noreply 496 assert _sanitize_filename("path/to\\file") == "path-to-file"
0981a08… noreply 497 assert _sanitize_filename("version.2") == "version-2"
0981a08… noreply 498
0981a08… noreply 499 def test_wiki_link(self):
0981a08… noreply 500 from video_processor.agent.skills.wiki_generator import _wiki_link
0981a08… noreply 501
0981a08… noreply 502 result = _wiki_link("My Page")
0981a08… noreply 503 assert result == "[My Page](My-Page)"
0981a08… noreply 504
0981a08… noreply 505 result = _wiki_link("Simple")
0981a08… noreply 506 assert result == "[Simple](Simple)"
0981a08… noreply 507
0981a08… noreply 508
0981a08… noreply 509 # ---------------------------------------------------------------------------
0981a08… noreply 510 # NotesExportSkill
0981a08… noreply 511 # ---------------------------------------------------------------------------
0981a08… noreply 512
0981a08… noreply 513
0981a08… noreply 514 class TestNotesExportSkill:
0981a08… noreply 515 def _sample_kg_data(self):
0981a08… noreply 516 return {
0981a08… noreply 517 "nodes": [
0981a08… noreply 518 {
0981a08… noreply 519 "name": "Python",
0981a08… noreply 520 "type": "technology",
0981a08… noreply 521 "descriptions": ["A programming language"],
0981a08… noreply 522 },
0981a08… noreply 523 {
0981a08… noreply 524 "name": "Alice",
0981a08… noreply 525 "type": "person",
0981a08… noreply 526 "descriptions": ["Lead developer"],
0981a08… noreply 527 },
0981a08… noreply 528 ],
0981a08… noreply 529 "relationships": [
0981a08… noreply 530 {"source": "Alice", "target": "Python", "type": "uses"},
0981a08… noreply 531 ],
0981a08… noreply 532 }
0981a08… noreply 533
0981a08… noreply 534 def test_export_to_obsidian(self, tmp_path):
0981a08… noreply 535 from video_processor.agent.skills.notes_export import export_to_obsidian
0981a08… noreply 536
0981a08… noreply 537 output_dir = tmp_path / "obsidian_vault"
0981a08… noreply 538 export_to_obsidian(self._sample_kg_data(), output_dir)
0981a08… noreply 539
0981a08… noreply 540 assert output_dir.is_dir()
0981a08… noreply 541
0981a08… noreply 542 # Check entity files exist
0981a08… noreply 543 python_file = output_dir / "Python.md"
0981a08… noreply 544 alice_file = output_dir / "Alice.md"
0981a08… noreply 545 assert python_file.exists()
0981a08… noreply 546 assert alice_file.exists()
0981a08… noreply 547
0981a08… noreply 548 # Check frontmatter in entity file
0981a08… noreply 549 python_content = python_file.read_text()
0981a08… noreply 550 assert "---" in python_content
0981a08… noreply 551 assert "type: technology" in python_content
0981a08… noreply 552 assert "# Python" in python_content
0981a08… noreply 553
0981a08… noreply 554 # Check wiki-links in Alice file
0981a08… noreply 555 alice_content = alice_file.read_text()
0981a08… noreply 556 assert "[[Python]]" in alice_content
0981a08… noreply 557 assert "uses" in alice_content
0981a08… noreply 558
0981a08… noreply 559 # Check index file
0981a08… noreply 560 index_file = output_dir / "_Index.md"
0981a08… noreply 561 assert index_file.exists()
0981a08… noreply 562 index_content = index_file.read_text()
0981a08… noreply 563 assert "[[Python]]" in index_content
0981a08… noreply 564 assert "[[Alice]]" in index_content
0981a08… noreply 565
0981a08… noreply 566 def test_export_to_obsidian_with_artifacts(self, tmp_path):
0981a08… noreply 567 from video_processor.agent.skills.notes_export import export_to_obsidian
0981a08… noreply 568
0981a08… noreply 569 art = Artifact(
0981a08… noreply 570 name="Test Plan",
0981a08… noreply 571 content="# Plan\n\nSteps here.",
0981a08… noreply 572 artifact_type="project_plan",
0981a08… noreply 573 format="markdown",
0981a08… noreply 574 )
0981a08… noreply 575 output_dir = tmp_path / "obsidian_arts"
0981a08… noreply 576 export_to_obsidian(self._sample_kg_data(), output_dir, artifacts=[art])
0981a08… noreply 577
0981a08… noreply 578 art_file = output_dir / "Test Plan.md"
0981a08… noreply 579 assert art_file.exists()
0981a08… noreply 580 art_content = art_file.read_text()
0981a08… noreply 581 assert "artifact" in art_content
0981a08… noreply 582 assert "Steps here." in art_content
0981a08… noreply 583
0981a08… noreply 584 def test_export_to_notion_md(self, tmp_path):
0981a08… noreply 585 from video_processor.agent.skills.notes_export import export_to_notion_md
0981a08… noreply 586
0981a08… noreply 587 output_dir = tmp_path / "notion_export"
0981a08… noreply 588 export_to_notion_md(self._sample_kg_data(), output_dir)
0981a08… noreply 589
0981a08… noreply 590 assert output_dir.is_dir()
0981a08… noreply 591
0981a08… noreply 592 # Check CSV database file
0981a08… noreply 593 csv_file = output_dir / "entities_database.csv"
0981a08… noreply 594 assert csv_file.exists()
0981a08… noreply 595 csv_content = csv_file.read_text()
0981a08… noreply 596 assert "Name" in csv_content
0981a08… noreply 597 assert "Type" in csv_content
0981a08… noreply 598 assert "Python" in csv_content
0981a08… noreply 599 assert "Alice" in csv_content
0981a08… noreply 600
0981a08… noreply 601 # Check entity markdown files
0981a08… noreply 602 python_file = output_dir / "Python.md"
0981a08… noreply 603 assert python_file.exists()
0981a08… noreply 604 python_content = python_file.read_text()
0981a08… noreply 605 assert "# Python" in python_content
0981a08… noreply 606 assert "technology" in python_content
0981a08… noreply 607
0981a08… noreply 608 # Check overview file
0981a08… noreply 609 overview_file = output_dir / "Overview.md"
0981a08… noreply 610 assert overview_file.exists()
0981a08… noreply 611
0981a08… noreply 612 def test_export_to_notion_md_with_artifacts(self, tmp_path):
0981a08… noreply 613 from video_processor.agent.skills.notes_export import export_to_notion_md
0981a08… noreply 614
0981a08… noreply 615 art = Artifact(
0981a08… noreply 616 name="Roadmap",
0981a08… noreply 617 content="# Roadmap\n\nQ1 goals.",
0981a08… noreply 618 artifact_type="roadmap",
0981a08… noreply 619 format="markdown",
0981a08… noreply 620 )
0981a08… noreply 621 output_dir = tmp_path / "notion_arts"
0981a08… noreply 622 export_to_notion_md(self._sample_kg_data(), output_dir, artifacts=[art])
0981a08… noreply 623
0981a08… noreply 624 art_file = output_dir / "Roadmap.md"
0981a08… noreply 625 assert art_file.exists()
0981a08… noreply 626 art_content = art_file.read_text()
0981a08… noreply 627 assert "Q1 goals." in art_content

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button