mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
169 lines
6.1 KiB
Python
169 lines
6.1 KiB
Python
"""Route-handler behavior for the trash lifecycle and the global pause switch,
|
|
called as coroutines (the established pattern in test_workflows_semantics.py).
|
|
CRUD create/patch + If-Match + the calendar endpoint already live in the
|
|
semantics suite; this covers the soft-delete -> restore -> purge flow and
|
|
pause-all / resume-all, including their 404 guards.
|
|
|
|
Run:
|
|
cd backend && .venv/bin/python -m pytest tests/test_workflows_api.py -v
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _wf_env(isolated_workflows_data, reset_scheduler_state, monkeypatch):
|
|
# Silence the ws fan-out the handlers fire; nothing is listening in-test.
|
|
from backend.apps.agents.core.ws_manager import ws_manager
|
|
|
|
async def p_noop(*args, **kwargs):
|
|
return None
|
|
|
|
monkeypatch.setattr(ws_manager, "broadcast_global", p_noop)
|
|
yield
|
|
|
|
|
|
def _run(coro):
|
|
return asyncio.new_event_loop().run_until_complete(coro)
|
|
|
|
|
|
def test_soft_delete_hides_and_disables_schedule(make_wf):
|
|
from backend.apps.workflows import storage
|
|
from backend.apps.workflows.workflows import delete_workflow
|
|
wf = make_wf()
|
|
wf.next_run_at = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
storage.save_workflow(wf)
|
|
|
|
res = _run(delete_workflow(wf.id))
|
|
assert res == {"ok": True}
|
|
after = storage.get_workflow(wf.id)
|
|
assert after.deleted_at is not None
|
|
assert after.schedule.enabled is False
|
|
assert after.next_run_at is None
|
|
assert wf.id not in {w.id for w in storage.list_workflows()}
|
|
assert wf.id in {w.id for w in storage.list_deleted_workflows()}
|
|
|
|
|
|
def test_soft_delete_drops_pending_missed(make_wf):
|
|
from backend.apps.workflows import storage
|
|
from backend.apps.workflows.workflows import delete_workflow
|
|
from backend.apps.workflows.models import MissedRun
|
|
wf = make_wf()
|
|
storage.save_workflow(wf)
|
|
storage.add_missed(MissedRun(workflow_id=wf.id, scheduled_for=datetime.now(timezone.utc)))
|
|
_run(delete_workflow(wf.id))
|
|
assert storage.list_missed() == []
|
|
|
|
|
|
def test_restore_brings_back_with_schedule_still_off(make_wf):
|
|
from backend.apps.workflows import storage
|
|
from backend.apps.workflows.workflows import delete_workflow, restore_workflow
|
|
wf = make_wf()
|
|
storage.save_workflow(wf)
|
|
_run(delete_workflow(wf.id))
|
|
enriched = _run(restore_workflow(wf.id))
|
|
assert enriched["id"] == wf.id
|
|
after = storage.get_workflow(wf.id)
|
|
assert after.deleted_at is None
|
|
assert after.schedule.enabled is False # restore is deliberate; user re-arms
|
|
assert wf.id in {w.id for w in storage.list_workflows()}
|
|
|
|
|
|
def test_purge_only_from_trash_and_removes_record(make_wf):
|
|
from backend.apps.workflows import storage
|
|
from backend.apps.workflows.workflows import delete_workflow, purge_workflow
|
|
wf = make_wf()
|
|
storage.save_workflow(wf)
|
|
_run(delete_workflow(wf.id))
|
|
res = _run(purge_workflow(wf.id))
|
|
assert res == {"ok": True}
|
|
assert storage.get_workflow(wf.id) is None
|
|
|
|
|
|
def test_delete_restore_purge_guards_404(make_wf):
|
|
from fastapi import HTTPException
|
|
from backend.apps.workflows import storage
|
|
from backend.apps.workflows.workflows import delete_workflow, restore_workflow, purge_workflow
|
|
wf = make_wf()
|
|
storage.save_workflow(wf)
|
|
|
|
# restore / purge refuse a workflow that isn't in trash.
|
|
for fn in (restore_workflow, purge_workflow):
|
|
with pytest.raises(HTTPException) as exc:
|
|
_run(fn(wf.id))
|
|
assert exc.value.status_code == 404
|
|
|
|
# double-delete is a 404 (already gone).
|
|
_run(delete_workflow(wf.id))
|
|
with pytest.raises(HTTPException) as exc:
|
|
_run(delete_workflow(wf.id))
|
|
assert exc.value.status_code == 404
|
|
|
|
|
|
def test_pause_all_and_resume_all_flip_global_flag(make_wf):
|
|
from backend.apps.workflows import storage
|
|
from backend.apps.workflows.workflows import pause_all_schedules, resume_all_schedules
|
|
assert storage.get_paused() is False
|
|
assert _run(pause_all_schedules()) == {"paused": True}
|
|
assert storage.get_paused() is True
|
|
assert _run(resume_all_schedules()) == {"paused": False}
|
|
assert storage.get_paused() is False
|
|
|
|
|
|
# ---- InvokeWorkflow (agents run a workflow as a tool and wait for the result) ----
|
|
|
|
def test_invoke_unknown_workflow_404():
|
|
from fastapi import HTTPException
|
|
from backend.apps.workflows.workflows import invoke_workflow
|
|
with pytest.raises(HTTPException) as ei:
|
|
_run(invoke_workflow("nope"))
|
|
assert ei.value.status_code == 404
|
|
|
|
|
|
def test_invoke_requires_the_exposed_opt_in(make_wf):
|
|
# A workflow the user never opted in must refuse: exposure is the whole permission model here.
|
|
from fastapi import HTTPException
|
|
from backend.apps.workflows import storage
|
|
from backend.apps.workflows.workflows import invoke_workflow
|
|
wf = make_wf()
|
|
storage.save_workflow(wf)
|
|
with pytest.raises(HTTPException) as ei:
|
|
_run(invoke_workflow(wf.id))
|
|
assert ei.value.status_code == 403
|
|
|
|
|
|
def test_invoke_waits_and_returns_transcript(make_wf, monkeypatch):
|
|
from backend.apps.workflows import storage, executor
|
|
from backend.apps.workflows.models import WorkflowRun
|
|
from backend.apps.workflows.workflows import invoke_workflow
|
|
wf = make_wf(exposed_as_tool=True)
|
|
storage.save_workflow(wf)
|
|
|
|
async def p_fake_execute(w, triggered_by="schedule", scheduled_for=None, tested_signature=None):
|
|
assert triggered_by == "manual"
|
|
return WorkflowRun(workflow_id=w.id, status="success", session_id="sess-invoke-1", cost_usd=0.02)
|
|
|
|
monkeypatch.setattr(executor, "execute", p_fake_execute)
|
|
|
|
from backend.apps.agents.agent_manager import agent_manager
|
|
|
|
class p_FakeMsg:
|
|
role = "assistant"
|
|
content = "invoked step done"
|
|
hidden = False
|
|
|
|
class p_FakeSess:
|
|
messages = [p_FakeMsg()]
|
|
|
|
monkeypatch.setitem(agent_manager.sessions, "sess-invoke-1", p_FakeSess())
|
|
res = _run(invoke_workflow(wf.id))
|
|
assert res["status"] == "success"
|
|
assert res["run_id"]
|
|
assert res["timed_out"] is False
|
|
assert "invoked step done" in res["transcript"]
|