fix(skill): 修复安装技能时配置持久化路径错误及单元测试失败的问题
- 修正 AgentConfigRepository 技能写入路径,确保技能保存至 config_json->context->skills - 修复 test_install_skill 单元测试的各种 mock 与断言,解决由于接口变更和依赖不匹配导致的失败
This commit is contained in:
parent
e153e86fd9
commit
554c83a5b4
@ -242,9 +242,14 @@ class AgentConfigRepository:
|
||||
sql = text("""
|
||||
UPDATE agent_configs
|
||||
SET config_json = jsonb_set(
|
||||
CAST(config_json AS jsonb),
|
||||
'{skills}',
|
||||
COALESCE(CAST(config_json->'skills' AS jsonb), CAST('[]' AS jsonb)) || CAST(:new_slugs_json AS jsonb),
|
||||
COALESCE(CAST(config_json AS jsonb), '{}'::jsonb),
|
||||
'{context}',
|
||||
jsonb_set(
|
||||
COALESCE(CAST(config_json->'context' AS jsonb), '{}'::jsonb),
|
||||
'{skills}',
|
||||
COALESCE(CAST(config_json->'context'->'skills' AS jsonb), CAST('[]' AS jsonb)) || CAST(:new_slugs_json AS jsonb),
|
||||
true
|
||||
),
|
||||
true
|
||||
)
|
||||
WHERE id = :id
|
||||
|
||||
@ -110,11 +110,11 @@ async def test_assert_admin_missing_user_raises_error():
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill.UserRepository") as mock_repo_cls:
|
||||
mock_repo = MagicMock()
|
||||
mock_repo.get_by_user_id = AsyncMock(return_value=mock_user)
|
||||
mock_repo.get_by_id_with_db = AsyncMock(return_value=mock_user)
|
||||
mock_repo_cls.return_value = mock_repo
|
||||
|
||||
with pytest.raises(ValueError, match="用户不存在"):
|
||||
await _assert_admin("non-existent-user")
|
||||
await _assert_admin(mock_session, "1")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -128,11 +128,11 @@ async def test_assert_admin_non_admin_raises_error():
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill.UserRepository") as mock_repo_cls:
|
||||
mock_repo = MagicMock()
|
||||
mock_repo.get_by_user_id = AsyncMock(return_value=mock_user)
|
||||
mock_repo.get_by_id_with_db = AsyncMock(return_value=mock_user)
|
||||
mock_repo_cls.return_value = mock_repo
|
||||
|
||||
with pytest.raises(ValueError, match="仅管理员可以安装 skill"):
|
||||
await _assert_admin("test-user")
|
||||
await _assert_admin(mock_session, "1")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -146,11 +146,11 @@ async def test_assert_admin_admin_passes():
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill.UserRepository") as mock_repo_cls:
|
||||
mock_repo = MagicMock()
|
||||
mock_repo.get_by_user_id = AsyncMock(return_value=mock_user)
|
||||
mock_repo.get_by_id_with_db = AsyncMock(return_value=mock_user)
|
||||
mock_repo_cls.return_value = mock_repo
|
||||
|
||||
# Should not raise
|
||||
await _assert_admin("test-admin-user")
|
||||
await _assert_admin(mock_session, "1")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -164,11 +164,11 @@ async def test_assert_admin_superadmin_passes():
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill.UserRepository") as mock_repo_cls:
|
||||
mock_repo = MagicMock()
|
||||
mock_repo.get_by_user_id = AsyncMock(return_value=mock_user)
|
||||
mock_repo.get_by_id_with_db = AsyncMock(return_value=mock_user)
|
||||
mock_repo_cls.return_value = mock_repo
|
||||
|
||||
# Should not raise
|
||||
await _assert_admin("test-superadmin-user")
|
||||
await _assert_admin(mock_session, "1")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@ -227,7 +227,8 @@ async def test_install_skill_no_context_returns_error():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_skill_git_no_skill_names_returns_error():
|
||||
@patch("yuxi.agents.toolkits.buildin.install_skill.pg_manager")
|
||||
async def test_install_skill_git_no_skill_names_returns_error(mock_pg):
|
||||
"""install_skill should return error Command when using Git source without skill_names."""
|
||||
runtime = MagicMock()
|
||||
runtime.context.thread_id = "test-thread-id"
|
||||
@ -252,7 +253,8 @@ async def test_install_skill_git_no_skill_names_returns_error():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_skill_git_with_skill_names_passes_admin_check():
|
||||
@patch("yuxi.agents.toolkits.buildin.install_skill.pg_manager")
|
||||
async def test_install_skill_git_with_skill_names_passes_admin_check(mock_pg):
|
||||
"""install_skill with Git source and skill_names should pass admin check (but may fail other steps)."""
|
||||
runtime = MagicMock()
|
||||
runtime.context.thread_id = "test-thread-id"
|
||||
@ -263,7 +265,7 @@ async def test_install_skill_git_with_skill_names_passes_admin_check():
|
||||
# Mock _assert_admin to not raise
|
||||
mock_assert.return_value = None
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill._install_git_skills") as mock_install:
|
||||
with patch("yuxi.services.remote_skill_install_service.install_remote_skills_batch") as mock_install:
|
||||
# Mock the git installation to return success
|
||||
mock_install.return_value = [{"slug": "test-skill", "success": True}]
|
||||
|
||||
@ -285,7 +287,8 @@ async def test_install_skill_git_with_skill_names_passes_admin_check():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_skill_sandbox_success():
|
||||
@patch("yuxi.agents.toolkits.buildin.install_skill.pg_manager")
|
||||
async def test_install_skill_sandbox_success(mock_pg):
|
||||
"""install_skill with valid sandbox path should work (full mock)."""
|
||||
runtime = MagicMock()
|
||||
runtime.context.thread_id = "test-thread-id"
|
||||
@ -296,7 +299,7 @@ async def test_install_skill_sandbox_success():
|
||||
mock_assert.return_value = None
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill._install_skill_from_sandbox") as mock_install:
|
||||
mock_install.return_value = "my-skill"
|
||||
mock_install.return_value = ("my-skill", False)
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill._enable_skill_in_current_config") as mock_enable:
|
||||
mock_enable.return_value = True
|
||||
@ -314,7 +317,8 @@ async def test_install_skill_sandbox_success():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_skill_value_error_handling():
|
||||
@patch("yuxi.agents.toolkits.buildin.install_skill.pg_manager")
|
||||
async def test_install_skill_value_error_handling(mock_pg):
|
||||
"""install_skill should handle ValueError from admin check gracefully."""
|
||||
runtime = MagicMock()
|
||||
runtime.context.thread_id = "test-thread-id"
|
||||
@ -338,7 +342,8 @@ async def test_install_skill_value_error_handling():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_skill_exception_handling():
|
||||
@patch("yuxi.agents.toolkits.buildin.install_skill.pg_manager")
|
||||
async def test_install_skill_exception_handling(mock_pg):
|
||||
"""install_skill should handle unexpected exceptions gracefully."""
|
||||
runtime = MagicMock()
|
||||
runtime.context.thread_id = "test-thread-id"
|
||||
@ -362,7 +367,8 @@ async def test_install_skill_exception_handling():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_skill_partial_config_failure():
|
||||
@patch("yuxi.agents.toolkits.buildin.install_skill.pg_manager")
|
||||
async def test_install_skill_partial_config_failure(mock_pg):
|
||||
"""install_skill should handle partial config persistence failure."""
|
||||
runtime = MagicMock()
|
||||
runtime.context.thread_id = "test-thread-id"
|
||||
@ -373,7 +379,7 @@ async def test_install_skill_partial_config_failure():
|
||||
mock_assert.return_value = None
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill._install_skill_from_sandbox") as mock_install:
|
||||
mock_install.return_value = "my-skill"
|
||||
mock_install.return_value = ("my-skill", False)
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill._enable_skill_in_current_config") as mock_enable:
|
||||
# Simulate config persistence failure
|
||||
@ -389,11 +395,12 @@ async def test_install_skill_partial_config_failure():
|
||||
assert isinstance(result, Command)
|
||||
result_str = str(result.update)
|
||||
# Should indicate config persistence issue
|
||||
assert "持久化" in result_str or "Skill 已安装" in result_str
|
||||
assert "当前会话配置中激活失败" in result_str or "技能已安装到系统" in result_str
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_skill_slug_warning_for_renamed():
|
||||
@patch("yuxi.agents.toolkits.buildin.install_skill.pg_manager")
|
||||
async def test_install_skill_slug_warning_for_renamed(mock_pg):
|
||||
"""install_skill should include warning when skill is renamed."""
|
||||
runtime = MagicMock()
|
||||
runtime.context.thread_id = "test-thread-id"
|
||||
@ -405,7 +412,7 @@ async def test_install_skill_slug_warning_for_renamed():
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill._install_skill_from_sandbox") as mock_install:
|
||||
# Simulate skill being renamed during installation
|
||||
mock_install.return_value = "my-skill-v2"
|
||||
mock_install.return_value = ("my-skill-v2", True)
|
||||
|
||||
with patch("yuxi.agents.toolkits.buildin.install_skill._enable_skill_in_current_config") as mock_enable:
|
||||
mock_enable.return_value = True
|
||||
|
||||
Loading…
Reference in New Issue
Block a user