galaxis-po/backend/tests/unit/test_skills.py
머니페니 76e3220e77
All checks were successful
Deploy to Production / deploy (push) Successful in 3m10s
feat: 에이전트 기능 추가 (LLM 서비스, 에이전트 API, 테스트)
2026-05-06 20:56:45 +09:00

210 lines
6.4 KiB
Python

"""Tests for agent skill types, loader, registry, and tool."""
from __future__ import annotations
import asyncio
from pathlib import Path
from unittest.mock import patch
import pytest
from app.agents.skills.types import SkillMetadata, Skill, SkillSource
from app.agents.skills.loader import SkillLoader
from app.agents.skills.registry import SkillRegistry
from app.agents.skills.tool import create_skill_tool
# ---------------------------------------------------------------------------
# SkillMetadata & Skill
# ---------------------------------------------------------------------------
class TestSkillMetadata:
def test_creation(self) -> None:
meta = SkillMetadata(
name="test-skill",
description="A test skill",
path="/skills/test.md",
source="builtin",
)
assert meta.name == "test-skill"
assert meta.description == "A test skill"
assert meta.path == "/skills/test.md"
assert meta.source == "builtin"
class TestSkill:
def test_creation_with_instructions(self) -> None:
skill = Skill(
name="s1",
description="desc",
path="/skills/s1.md",
source="builtin",
instructions="Do this and that.",
)
assert skill.name == "s1"
assert skill.instructions == "Do this and that."
assert skill.source == "builtin"
# ---------------------------------------------------------------------------
# SkillLoader
# ---------------------------------------------------------------------------
_VALID_SKILL_CONTENT = """\
---
name: test-skill
description: A skill for testing
---
## Instructions
Follow these steps to perform the test skill.
1. Step one
2. Step two
"""
_NO_FRONTMATTER_CONTENT = """\
## Instructions
This file has no YAML frontmatter.
"""
_EMPTY_FRONTMATTER_CONTENT = """\
---
---
## Instructions
Empty frontmatter.
"""
class TestSkillLoader:
def test_parse_valid(self) -> None:
skill = SkillLoader.parse_skill_file(
content=_VALID_SKILL_CONTENT,
path="/skills/test-skill.md",
source="builtin",
)
assert skill.name == "test-skill"
assert skill.description == "A skill for testing"
assert "Step one" in skill.instructions
assert skill.source == "builtin"
def test_parse_no_frontmatter(self) -> None:
"""frontmatter가 없으면 name/description이 빈 문자열."""
skill = SkillLoader.parse_skill_file(
content=_NO_FRONTMATTER_CONTENT,
path="/skills/no-front.md",
source="builtin",
)
assert skill.name == ""
assert skill.description == ""
assert "no YAML frontmatter" in skill.instructions
def test_parse_empty_frontmatter(self) -> None:
"""빈 frontmatter도 정상 처리."""
skill = SkillLoader.parse_skill_file(
content=_EMPTY_FRONTMATTER_CONTENT,
path="/skills/empty-front.md",
source="builtin",
)
assert skill.name == ""
assert skill.description == ""
def test_load_from_path(self, tmp_path: Path) -> None:
skill_file = tmp_path / "SKILL.md"
skill_file.write_text(_VALID_SKILL_CONTENT, encoding="utf-8")
skill = SkillLoader.load_from_path(skill_file, "builtin")
assert skill.name == "test-skill"
assert "Step one" in skill.instructions
def test_extract_metadata(self, tmp_path: Path) -> None:
skill_file = tmp_path / "SKILL.md"
skill_file.write_text(_VALID_SKILL_CONTENT, encoding="utf-8")
meta = SkillLoader.extract_metadata(skill_file, "builtin")
assert isinstance(meta, SkillMetadata)
assert meta.name == "test-skill"
assert meta.description == "A skill for testing"
# ---------------------------------------------------------------------------
# SkillRegistry
# ---------------------------------------------------------------------------
class TestSkillRegistry:
def setup_method(self) -> None:
SkillRegistry.clear_cache()
def test_discover_finds_builtin_skills(self) -> None:
"""실제 builtin 디렉토리에서 dcf-kr, kim-jong-bong-strategy 발견."""
skills = SkillRegistry.discover()
names = [s.name for s in skills]
assert "dcf-kr" in names
assert "kim-jong-bong-strategy" in names
def test_get_returns_skill(self) -> None:
SkillRegistry.discover()
skill = SkillRegistry.get("dcf-kr")
assert skill is not None
assert skill.name == "dcf-kr"
assert "WACC" in skill.instructions
def test_get_returns_none_for_unknown(self) -> None:
SkillRegistry.discover()
assert SkillRegistry.get("nonexistent-skill") is None
def test_list_skills(self) -> None:
SkillRegistry.discover()
skills = SkillRegistry.list_skills()
assert len(skills) >= 2
def test_build_skills_section(self) -> None:
SkillRegistry.discover()
section = SkillRegistry.build_skills_section()
assert "dcf-kr" in section
assert "kim-jong-bong-strategy" in section
def test_build_skills_section_empty(self) -> None:
"""캐시 비어있으면 빈 문자열 반환."""
section = SkillRegistry.build_skills_section()
assert section == ""
def test_clear_cache(self) -> None:
SkillRegistry.discover()
assert len(SkillRegistry.list_skills()) >= 2
SkillRegistry.clear_cache()
assert len(SkillRegistry._cache) == 0
# ---------------------------------------------------------------------------
# create_skill_tool
# ---------------------------------------------------------------------------
class TestSkillTool:
def setup_method(self) -> None:
SkillRegistry.clear_cache()
SkillRegistry.discover()
def test_tool_metadata(self) -> None:
tool = create_skill_tool()
assert tool.name == "use_skill"
assert tool.concurrency_safe is True
def test_execute_valid_skill(self) -> None:
tool = create_skill_tool()
result = asyncio.run(tool.execute({"skill_name": "dcf-kr"}))
assert "WACC" in result.data
def test_execute_unknown_skill(self) -> None:
tool = create_skill_tool()
result = asyncio.run(tool.execute({"skill_name": "no-such-skill"}))
assert "찾을 수 없습니다" in result.data
assert "dcf-kr" in result.data