"""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