"""数据库层单元测试。 直接对 Database 方法调用真实 SQLite 路径,覆盖节点、实例、工作流、 版本、任务与产物的增删改查。 """ from pathlib import Path from app.db import Database from wov_sdk.models import NodeManifest def manifest() -> NodeManifest: """构造最小合法节点 manifest 供 CRUD 测试复用。""" return NodeManifest( id="echo", name="Echo", version="1.0.0", capability="echo", command=["python", "-m", "echo"], repo_dir="wov-node-echo", ) def test_node_crud(tmp_path) -> None: """验证节点的注册、覆盖更新与删除。""" db = Database(tmp_path / "wov.db") assert db.get_node("echo") is None assert db.list_nodes() == [] db.upsert_node(manifest()) assert db.get_node("echo") == manifest() assert db.list_nodes() == [manifest()] updated = manifest() updated.version = "1.1.0" db.upsert_node(updated) assert db.get_node("echo").version == "1.1.0" db.delete_node("echo") assert db.get_node("echo") is None def test_instance_crud(tmp_path) -> None: """验证节点实例记录的插入、状态更新与删除。""" db = Database(tmp_path / "wov.db") db.upsert_node(manifest()) instance = { "id": "ni_1", "node_id": "echo", "status": "ready", "pid": 123, "address": "http://127.0.0.1:1", "started_at": "2026-01-01T00:00:00+00:00", "last_used_at": "2026-01-01T00:00:00+00:00", "busy_since": None, "error": None, } db.upsert_instance(instance) assert db.list_instances() == [instance] instance["status"] = "stopped" db.upsert_instance(instance) assert db.list_instances()[0]["status"] == "stopped" db.delete_instance("ni_1") assert db.list_instances() == [] def test_workflow_crud(tmp_path) -> None: """验证工作流概要的插入、发布标记更新与删除。""" db = Database(tmp_path / "wov.db") workflow = { "id": "demo", "name": "Demo", "description": "desc", "published": 0, "latest_version": 0, } db.upsert_workflow(workflow) assert db.get_workflow("demo")["name"] == "Demo" assert [item["id"] for item in db.list_workflows()] == ["demo"] db.upsert_workflow({**workflow, "published": 1, "latest_version": 1}) assert db.get_workflow("demo")["published"] == 1 db.delete_workflow("demo") assert db.get_workflow("demo") is None def test_workflow_versions(tmp_path) -> None: """验证工作流版本的写入、最新版本查询与列表。""" db = Database(tmp_path / "wov.db") db.upsert_workflow( {"id": "demo", "name": "Demo", "published": 1, "latest_version": 2} ) definition = {"name": "Demo", "version": 1, "nodes": [], "edges": []} db.create_workflow_version("demo", 1, definition) db.create_workflow_version("demo", 2, {**definition, "version": 2}) latest = db.get_latest_workflow_version("demo") assert latest["version"] == 2 assert latest["definition"]["version"] == 2 version = db.get_workflow_version("demo", 1) assert version["version"] == 1 assert db.get_workflow_version("demo", 99) is None assert len(db.list_workflow_versions("demo")) == 2 empty_db = Database(tmp_path / "empty.db") assert empty_db.get_latest_workflow_version("missing") is None def test_run_and_artifact_crud(tmp_path) -> None: """验证任务与产物的创建、查询、更新与删除。""" db = Database(tmp_path / "wov.db") db.upsert_workflow({"id": "demo", "name": "Demo", "published": 1, "latest_version": 1}) now = "2026-01-01T00:00:00+00:00" db.create_run( { "id": "run_1", "workflow_id": "demo", "workflow_version": 1, "status": "QUEUED", "progress": 0, "input_uri": "in.txt", "created_at": now, "updated_at": now, } ) assert db.get_run("run_1")["status"] == "QUEUED" assert db.next_queued_run()["id"] == "run_1" db.update_run("run_1", status="RUNNING", progress=0.5, updated_at=now) db.update_run("run_1") assert db.get_run("run_1")["status"] == "RUNNING" assert db.get_run("run_1")["progress"] == 0.5 assert db.next_queued_run() is None assert len(db.list_runs()) == 1 db.create_artifact( { "run_id": "run_1", "node_id": "echo", "name": "result", "uri": "out.txt", "mime_type": "text/plain", "size": 3, } ) assert db.get_artifact("run_1", "result")["uri"] == "out.txt" assert db.get_artifact("run_1", "missing") is None assert len(db.list_artifacts("run_1")) == 1 db.delete_run_artifacts("run_1") assert db.list_artifacts("run_1") == [] def test_reset_run_clears_error_and_artifacts(tmp_path) -> None: """验证 reset_run 会把失败任务恢复到排队状态并清空旧产物。""" db = Database(tmp_path / "wov.db") db.upsert_workflow({"id": "demo", "name": "Demo", "published": 1, "latest_version": 1}) now = "2026-01-01T00:00:00+00:00" db.create_run( { "id": "run_1", "workflow_id": "demo", "workflow_version": 1, "status": "FAILED", "progress": 0.75, "current_node_id": "translate", "error": "timed out", "input_uri": "in.txt", "created_at": now, "updated_at": now, } ) db.create_artifact( { "run_id": "run_1", "node_id": "asr", "name": "asr.srt_uri", "uri": "out.srt", "mime_type": "application/x-subrip", "size": 3, } ) db.reset_run("run_1", "2026-01-02T00:00:00+00:00") run = db.get_run("run_1") assert run["status"] == "QUEUED" assert run["progress"] == 0 assert run["current_node_id"] is None assert run["error"] is None assert run["updated_at"] == "2026-01-02T00:00:00+00:00" assert run["created_at"] == now assert db.list_artifacts("run_1") == []