实现 Skills 管理功能,支持超级管理员导入、编辑、导出和删除技能包。 - 新增 Skill 数据模型(元数据存数据库,内容存文件系统) - 新增 /api/system/skills/* 路由(权限分级:admin 可列表,superadmin 可管理) - 新增 SelectedSkillsReadonlyBackend,运行时只读挂载到 /skills - Agent 配置新增 skills 字段,运行时注入技能提示词 - 前端新增 Skills 管理页面(设置弹窗 + 独立路由) - 新增测试覆盖:service/router/backend 单测 未实现功能: - 关联使用其他技能 - 沙箱执行代码 - 在技能中绑定工具
100 lines
3.0 KiB
Python
100 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.testclient import TestClient
|
|
|
|
from server.routers.skill_router import skills
|
|
from server.utils.auth_middleware import get_admin_user, get_db, get_superadmin_user
|
|
from src.storage.postgres.models_business import Skill, User
|
|
|
|
|
|
def _build_app(*, allow_superadmin: bool) -> FastAPI:
|
|
app = FastAPI()
|
|
app.include_router(skills, prefix="/api")
|
|
|
|
async def fake_db():
|
|
return None
|
|
|
|
async def fake_admin_user():
|
|
return User(
|
|
username="admin",
|
|
user_id="admin",
|
|
password_hash="x",
|
|
role="admin",
|
|
)
|
|
|
|
async def fake_superadmin_user():
|
|
if not allow_superadmin:
|
|
raise HTTPException(status_code=403, detail="需要超级管理员权限")
|
|
return User(
|
|
username="root",
|
|
user_id="root",
|
|
password_hash="x",
|
|
role="superadmin",
|
|
)
|
|
|
|
app.dependency_overrides[get_db] = fake_db
|
|
app.dependency_overrides[get_admin_user] = fake_admin_user
|
|
app.dependency_overrides[get_superadmin_user] = fake_superadmin_user
|
|
return app
|
|
|
|
|
|
def test_list_skills_route_returns_data(monkeypatch):
|
|
async def fake_list_skills(_db):
|
|
return [
|
|
Skill(
|
|
slug="demo",
|
|
name="demo",
|
|
description="demo skill",
|
|
dir_path="skills/demo",
|
|
)
|
|
]
|
|
|
|
monkeypatch.setattr("server.routers.skill_router.list_skills", fake_list_skills)
|
|
|
|
app = _build_app(allow_superadmin=True)
|
|
client = TestClient(app)
|
|
resp = client.get("/api/system/skills")
|
|
assert resp.status_code == 200, resp.text
|
|
payload = resp.json()
|
|
assert payload["success"] is True
|
|
assert payload["data"][0]["slug"] == "demo"
|
|
|
|
|
|
def test_import_skill_requires_superadmin():
|
|
app = _build_app(allow_superadmin=False)
|
|
client = TestClient(app)
|
|
|
|
resp = client.post(
|
|
"/api/system/skills/import",
|
|
files={"file": ("demo.zip", b"not zip", "application/zip")},
|
|
)
|
|
assert resp.status_code == 403
|
|
|
|
|
|
def test_update_skill_file_passes_operator(monkeypatch):
|
|
captured: dict[str, str] = {}
|
|
|
|
async def fake_update_skill_file(_db, *, slug, relative_path, content, updated_by):
|
|
captured["slug"] = slug
|
|
captured["relative_path"] = relative_path
|
|
captured["content"] = content
|
|
captured["updated_by"] = updated_by
|
|
|
|
monkeypatch.setattr("server.routers.skill_router.update_skill_file", fake_update_skill_file)
|
|
|
|
app = _build_app(allow_superadmin=True)
|
|
client = TestClient(app)
|
|
|
|
resp = client.put(
|
|
"/api/system/skills/demo/file",
|
|
json={
|
|
"path": "SKILL.md",
|
|
"content": "---\nname: demo\ndescription: demo\n---\n# Demo\n",
|
|
},
|
|
)
|
|
assert resp.status_code == 200, resp.text
|
|
assert captured["slug"] == "demo"
|
|
assert captured["relative_path"] == "SKILL.md"
|
|
assert captured["updated_by"] == "root"
|