deer-flow/backend/tests/test_migration_0015_scheduled_task_enqueue.py
Wenchao An 0b3dadbc9b
feat(subagents): add acceptance checks to durable batch items (#5289)
* feat(subagents): check and persist durable batch acceptance

Carry optional per-item criteria into native subagents, reuse the deterministic checker, and expose separate verdicts through item queries and exports. Preserve execution and retry semantics, renew leases during checks, and migrate existing batch rows with nullable acceptance fields.

* fix(subagents): align batch acceptance normalization and sandbox admission

* test(auth): include project permissions in the full-stack contract
2026-09-09 08:45:20 +08:00

70 lines
3.8 KiB
Python

from __future__ import annotations
from pathlib import Path
import pytest
import sqlalchemy as sa
from sqlalchemy.ext.asyncio import create_async_engine
import deerflow.persistence.models # noqa: F401
from deerflow.persistence.base import Base
from deerflow.persistence.bootstrap import bootstrap_schema
pytestmark = pytest.mark.asyncio
async def test_migration_interrupts_legacy_queue_and_adds_claim_fields(tmp_path: Path) -> None:
db_path = tmp_path / "deer.db"
sync = sa.create_engine(f"sqlite:///{db_path}")
try:
Base.metadata.create_all(sync)
with sync.begin() as conn:
conn.execute(sa.text("DROP INDEX uq_scheduled_task_run_active"))
conn.execute(sa.text("ALTER TABLE scheduled_task_runs DROP COLUMN attempt_count"))
conn.execute(sa.text("ALTER TABLE scheduled_task_runs DROP COLUMN lease_expires_at"))
conn.execute(sa.text("ALTER TABLE scheduled_task_runs DROP COLUMN lease_owner"))
conn.execute(sa.text("CREATE UNIQUE INDEX uq_scheduled_task_run_active ON scheduled_task_runs (task_id) WHERE status IN ('queued', 'running')"))
conn.execute(
sa.text(
"INSERT INTO scheduled_tasks "
"(id, user_id, thread_id, context_mode, assistant_id, title, prompt, "
"schedule_type, schedule_spec, timezone, status, overlap_policy, run_count, created_at, updated_at) "
"VALUES ('task-legacy', 'user-1', 'thread-1', 'reuse_thread', 'lead_agent', "
"'Legacy', 'Prompt', 'cron', '{\"cron\":\"0 9 * * *\"}', 'UTC', "
"'enabled', 'skip', 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
)
)
conn.execute(
sa.text(
"INSERT INTO scheduled_task_runs (id, task_id, thread_id, scheduled_for, trigger, status, created_at) VALUES ('run-legacy-queued', 'task-legacy', 'thread-1', CURRENT_TIMESTAMP, 'scheduled', 'queued', CURRENT_TIMESTAMP)"
)
)
conn.execute(sa.text("CREATE TABLE IF NOT EXISTS alembic_version (version_num VARCHAR(32) NOT NULL)"))
conn.execute(sa.text("DELETE FROM alembic_version"))
conn.execute(sa.text("INSERT INTO alembic_version (version_num) VALUES ('0013_mcp_task_notifications')"))
finally:
sync.dispose()
engine = create_async_engine(f"sqlite+aiosqlite:///{db_path}")
try:
await bootstrap_schema(engine, backend="sqlite")
async with engine.connect() as conn:
columns = {column["name"]: column for column in await conn.run_sync(lambda connection: sa.inspect(connection).get_columns("scheduled_task_runs"))}
version = await conn.scalar(sa.text("SELECT version_num FROM alembic_version"))
overlap_policy = await conn.scalar(sa.text("SELECT overlap_policy FROM scheduled_tasks WHERE id = 'task-legacy'"))
legacy_run = (await conn.execute(sa.text("SELECT status, error, finished_at FROM scheduled_task_runs WHERE id = 'run-legacy-queued'"))).one()
index_sql = await conn.scalar(sa.text("SELECT sql FROM sqlite_master WHERE type = 'index' AND name = 'uq_scheduled_task_run_active'"))
# Bootstrap always advances to the repository head after exercising
# the 0015 migration behavior below.
assert version == "0021_batch_acceptance"
assert {"lease_owner", "lease_expires_at", "attempt_count"} <= columns.keys()
assert columns["attempt_count"]["nullable"] is False
assert overlap_policy == "enqueue"
assert legacy_run.status == "interrupted"
assert "gateway upgraded" in legacy_run.error
assert legacy_run.finished_at is not None
assert "'launching'" in index_sql
finally:
await engine.dispose()