feat: 新增工具插件、系统日志、workflow配置及前端优化
1. 新增工具插件(edit_file, python_executor, search_file, shell_executor, write_file) 2. 新增系统事件日志模块和API 3. 新增workflow配置文件和详情API 4. 前端增加SSE、错误边界、设置引导等组件 5. 优化认证加密、速率限制、配置加载等工具模块 6. 删除废弃的cluster和health API 7. 补充单元测试和集成测试 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""workflow 路由鉴权测试:SSE / reply / resume / detail / graph 端点归属校验。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import types
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from httpx import AsyncClient, ASGITransport
|
||||
|
||||
from kilostar.api.workflow import workflow_router
|
||||
from kilostar.utils.access import Accessor, TokenData
|
||||
|
||||
|
||||
def _fake_user(user_id: str = "alice"):
|
||||
return TokenData(user_id=user_id, username=user_id)
|
||||
|
||||
|
||||
def _make_workflow(owner: str = "alice"):
|
||||
return types.SimpleNamespace(
|
||||
trace_id="trace-1",
|
||||
user_id=owner,
|
||||
title="test",
|
||||
status="running",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app_alice():
|
||||
app = FastAPI()
|
||||
app.include_router(workflow_router)
|
||||
app.dependency_overrides[Accessor.get_current_user] = lambda: _fake_user("alice")
|
||||
return app
|
||||
|
||||
|
||||
def _register_pg(fake_actors, owner: str = "alice"):
|
||||
pg = types.SimpleNamespace()
|
||||
pg.get_workflow = types.SimpleNamespace(remote=AsyncMock(return_value=_make_workflow(owner)))
|
||||
pg.get_workflow_context = types.SimpleNamespace(remote=AsyncMock(return_value=None))
|
||||
pg.get_workflow_graph_state = types.SimpleNamespace(remote=AsyncMock(return_value=None))
|
||||
fake_actors.register("postgres_database", pg)
|
||||
return pg
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_detail_forbidden_other_user(app_alice, fake_actors):
|
||||
_register_pg(fake_actors, owner="bob")
|
||||
async with AsyncClient(transport=ASGITransport(app=app_alice), base_url="http://t") as c:
|
||||
resp = await c.get("/api/v1/workflow/trace-1")
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_detail_not_found(app_alice, fake_actors):
|
||||
pg = types.SimpleNamespace()
|
||||
pg.get_workflow = types.SimpleNamespace(remote=AsyncMock(return_value=None))
|
||||
fake_actors.register("postgres_database", pg)
|
||||
async with AsyncClient(transport=ASGITransport(app=app_alice), base_url="http://t") as c:
|
||||
resp = await c.get("/api/v1/workflow/trace-nonexist")
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reply_forbidden_other_user(app_alice, fake_actors):
|
||||
_register_pg(fake_actors, owner="bob")
|
||||
async with AsyncClient(transport=ASGITransport(app=app_alice), base_url="http://t") as c:
|
||||
resp = await c.post("/api/v1/workflow/reply/trace-1", json={"message": "hi"})
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resume_forbidden_other_user(app_alice, fake_actors):
|
||||
_register_pg(fake_actors, owner="bob")
|
||||
async with AsyncClient(transport=ASGITransport(app=app_alice), base_url="http://t") as c:
|
||||
resp = await c.post("/api/v1/workflow/trace-1/resume")
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resume_not_found(app_alice, fake_actors):
|
||||
pg = types.SimpleNamespace()
|
||||
pg.get_workflow = types.SimpleNamespace(remote=AsyncMock(return_value=None))
|
||||
fake_actors.register("postgres_database", pg)
|
||||
async with AsyncClient(transport=ASGITransport(app=app_alice), base_url="http://t") as c:
|
||||
resp = await c.post("/api/v1/workflow/trace-nonexist/resume")
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_graph_forbidden_other_user(app_alice, fake_actors):
|
||||
_register_pg(fake_actors, owner="bob")
|
||||
async with AsyncClient(transport=ASGITransport(app=app_alice), base_url="http://t") as c:
|
||||
resp = await c.get("/api/v1/workflow/trace-1/graph")
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sse_forbidden_other_user(app_alice, fake_actors):
|
||||
_register_pg(fake_actors, owner="bob")
|
||||
async with AsyncClient(transport=ASGITransport(app=app_alice), base_url="http://t") as c:
|
||||
resp = await c.get("/api/v1/workflow/sse/trace-1")
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sse_not_found(app_alice, fake_actors):
|
||||
pg = types.SimpleNamespace()
|
||||
pg.get_workflow = types.SimpleNamespace(remote=AsyncMock(return_value=None))
|
||||
fake_actors.register("postgres_database", pg)
|
||||
async with AsyncClient(transport=ASGITransport(app=app_alice), base_url="http://t") as c:
|
||||
resp = await c.get("/api/v1/workflow/sse/trace-nonexist")
|
||||
assert resp.status_code == 404
|
||||
Reference in New Issue
Block a user