# -*- coding: utf-8 -*- """任务中心·扫码接单:TaskService 状态机 + Repository 访问(自包含内存库)。""" from __future__ import annotations from contextlib import asynccontextmanager import pytest from fastapi import HTTPException from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine from app.infrastructure.models import Base # noqa: F401 (注册表到 Base) from app.infrastructure.repositories import Database from app.services.task_service import TaskService @asynccontextmanager async def _db(): engine = create_async_engine("sqlite+aiosqlite:///:memory:") async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) Session = async_sessionmaker(engine, expire_on_commit=False) async with Session() as session: yield Database(session=session) await engine.dispose() def _grab_published(**kw): return dict( task_code="TK-004-GRAB", title="短视频剪辑", mode="grab", status="published", category="技术开发", budget_min=1500, budget_max=3000, display_priority=30, **kw, ) async def test_claim_marks_claimed_and_records_claim(): """published + grab → claimed(claimed_by/claimed_at),并写 task_claims 流水。""" async with _db() as db: t = await db.tasks.create(_grab_published()) actor = {"id": "u_opc_1", "username": "opc01", "nickname": "阿健"} res = await TaskService(db).claim(t["task_code"], actor, source="scan") assert res["status"] == "claimed" assert res["claimed_by"] == "u_opc_1" assert res["claimed_at"] claims = await db.task_claims.list_by_task(t["id"]) assert claims and claims[0]["claim_source"] == "scan" async def test_claim_disallowed_non_grab_mode(): """bid 模式不可扫码接单。""" async with _db() as db: t = await db.tasks.create(_grab_published(title="UI", mode="bid")) with pytest.raises(HTTPException): await TaskService(db).claim(t["task_code"], {"id": "u1"}, "scan") async def test_claim_unknown_code_404(): async with _db() as db: with pytest.raises(HTTPException): await TaskService(db).claim("TK-NOT-EXIST", {"id": "u1"}, "scan") async def test_claim_already_claimed_rejected(): """已 claim 的任务再 claim 应报错。""" async with _db() as db: t = await db.tasks.create(_grab_published()) await TaskService(db).claim(t["task_code"], {"id": "u1"}, "scan") with pytest.raises(HTTPException): await TaskService(db).claim(t["task_code"], {"id": "u2"}, "scan") async def test_doing_then_complete(): """claimed → doing → completed。""" async with _db() as db: t = await db.tasks.create(_grab_published()) actor = {"id": "u_opc_1", "username": "opc01", "nickname": "阿健"} await TaskService(db).claim(t["task_code"], actor, source="scan") doing = await TaskService(db).start_doing(t["id"], actor) assert doing["status"] == "doing" and doing["doing_at"] done = await TaskService(db).complete(t["id"], actor) assert done["status"] == "completed" async def test_list_published_returns_display_fields(): """大屏取数:list_published 返回新字段且按 display_priority 降序。""" async with _db() as db: await db.tasks.create(_grab_published(task_code="TK-LOW", display_priority=1)) await db.tasks.create(_grab_published(task_code="TK-HIGH", display_priority=99)) items = await db.tasks.list_published() codes = [t["task_code"] for t in items] assert "TK-LOW" in codes and "TK-HIGH" in codes assert codes.index("TK-HIGH") < codes.index("TK-LOW") assert all("task_code" in t and "claimed_by" in t for t in items)