Files
JARVIS/backend/tests/backend/app/test_task_router.py

222 lines
8.3 KiB
Python

import asyncio
import sys
from datetime import UTC, date, datetime
from unittest.mock import Mock
import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
sys.modules.setdefault('psutil', Mock())
import app.models # noqa: F401
from app.database import Base, get_db
from app.models.user import User
from app.routers.auth import get_current_user
from app.routers.schedule_center import router as schedule_center_router
from app.routers.task import router as task_router
from app.services.auth_service import get_password_hash
@pytest.fixture
async def task_env(tmp_path):
db_path = tmp_path / 'test_task_router.db'
engine = create_async_engine(f"sqlite+aiosqlite:///{db_path}", future=True)
session_factory = async_sessionmaker(engine, expire_on_commit=False)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async with session_factory() as session:
user = User(
username='task_user',
email='task@example.com',
hashed_password=get_password_hash('secret123'),
full_name='Task Tester',
)
session.add(user)
await session.commit()
await session.refresh(user)
async def override_get_db():
async with session_factory() as session:
yield session
async def override_get_current_user():
return user
test_app = FastAPI()
test_app.include_router(task_router)
test_app.include_router(schedule_center_router)
test_app.dependency_overrides[get_db] = override_get_db
test_app.dependency_overrides[get_current_user] = override_get_current_user
try:
yield test_app
finally:
await engine.dispose()
@pytest.mark.asyncio
async def test_create_task_and_get_detail_with_business_fields(task_env):
transport = ASGITransport(app=task_env)
async with AsyncClient(transport=transport, base_url='http://testserver') as client:
create_response = await client.post(
'/api/tasks',
json={
'title': 'Prepare daily status',
'description': 'Assemble the updated today status payload',
'priority': 'high',
'due_date': '2026-04-10T09:00:00Z',
'tags': ['today-status', 'chat'],
'source': 'chat',
'conversation_id': 'conv-123',
'quadrant': 'urgent-important',
'assignee_type': 'commander',
'assignee_id': 'code_commander',
},
)
assert create_response.status_code == 201
created = create_response.json()
detail_response = await client.get(f"/api/tasks/{created['id']}")
assert detail_response.status_code == 200
payload = detail_response.json()
assert payload['title'] == 'Prepare daily status'
assert payload['tags'] == ['today-status', 'chat']
assert payload['source'] == 'chat'
assert payload['conversation_id'] == 'conv-123'
assert payload['quadrant'] == 'urgent-important'
assert payload['assignee_type'] == 'commander'
assert payload['assignee_id'] == 'code_commander'
assert payload['dispatch']['status'] == 'idle'
assert [item['action'] for item in payload['history'][:2]] == ['created_from_chat', 'created']
@pytest.mark.asyncio
async def test_subtask_crud_and_reorder(task_env):
transport = ASGITransport(app=task_env)
async with AsyncClient(transport=transport, base_url='http://testserver') as client:
task_response = await client.post(
'/api/tasks',
json={
'title': 'Implement kanban detail',
'due_date': '2026-04-10T11:00:00Z',
'quadrant': 'not-urgent-important',
},
)
task_id = task_response.json()['id']
first_subtask = await client.post(
f'/api/tasks/{task_id}/subtasks',
json={'title': 'Load task detail', 'assignee_type': 'agent', 'assignee_id': 'planner'},
)
second_subtask = await client.post(
f'/api/tasks/{task_id}/subtasks',
json={'title': 'Persist task edits', 'assignee_type': 'agent', 'assignee_id': 'executor'},
)
first_id = first_subtask.json()['subtasks'][0]['id']
second_id = second_subtask.json()['subtasks'][1]['id']
update_response = await client.patch(
f'/api/tasks/{task_id}/subtasks/{first_id}',
json={'status': 'done'},
)
reorder_response = await client.post(
f'/api/tasks/{task_id}/subtasks/reorder',
json={'items': [{'id': first_id, 'order_index': 1}, {'id': second_id, 'order_index': 0}]},
)
assert update_response.status_code == 200
assert reorder_response.status_code == 200
reordered = reorder_response.json()
assert [item['id'] for item in reordered['subtasks']] == [second_id, first_id]
assert reordered['subtasks'][1]['status'] == 'done'
assert any(item['action'] == 'subtask_reordered' for item in reordered['history'])
@pytest.mark.asyncio
async def test_create_today_status_task_persists_status_and_subtasks(task_env):
transport = ASGITransport(app=task_env)
async with AsyncClient(transport=transport, base_url='http://testserver') as client:
create_response = await client.post(
'/api/tasks',
json={
'title': '今日看板任务',
'description': '来自今日状态看板',
'status': 'in_progress',
'priority': 'high',
'source': 'today_status',
'quadrant': 'urgent-important',
'subtasks': [
{'title': '子任务一', 'status': 'todo'},
{'title': '子任务二', 'status': 'done'},
],
},
)
assert create_response.status_code == 201
payload = create_response.json()
detail_response = await client.get(f"/api/tasks/{payload['id']}")
assert detail_response.status_code == 200
detail_payload = detail_response.json()
assert detail_payload['status'] == 'in_progress'
assert detail_payload['source'] == 'today_status'
assert detail_payload['quadrant'] == 'urgent-important'
assert [item['title'] for item in detail_payload['subtasks']] == ['子任务一', '子任务二']
assert detail_payload['subtasks'][0]['order_index'] == 0
assert detail_payload['subtasks'][1]['order_index'] == 1
assert detail_payload['subtasks'][1]['status'] == 'done'
assert detail_payload['subtasks'][1]['completed_at'] is not None
@pytest.mark.asyncio
async def test_dispatch_updates_task_and_schedule_center_summary(task_env):
transport = ASGITransport(app=task_env)
async with AsyncClient(transport=transport, base_url='http://testserver') as client:
task_response = await client.post(
'/api/tasks',
json={
'title': 'Dispatch to commander',
'priority': 'urgent',
'due_date': '2026-04-10T15:00:00Z',
'quadrant': 'urgent-important',
},
)
task_id = task_response.json()['id']
dispatch_response = await client.post(
f'/api/tasks/{task_id}/dispatch',
json={'target': 'commander'},
)
assert dispatch_response.status_code == 200
assert dispatch_response.json()['task']['dispatch']['status'] == 'queued'
await asyncio.sleep(0.18)
detail_response = await client.get(f'/api/tasks/{task_id}')
date_response = await client.get('/api/schedule-center/date', params={'date_str': '2026-04-10'})
assert detail_response.status_code == 200
detail = detail_response.json()
assert detail['dispatch']['status'] == 'completed'
assert detail['status'] == 'done'
assert detail['dispatch']['run_id']
assert detail['dispatch']['result_summary']
assert date_response.status_code == 200
payload = date_response.json()
assert payload['commander_summary'] == {
'total': 1,
'queued': 0,
'running': 0,
'completed': 1,
'failed': 0,
}
assert payload['focus_tasks'][0]['id'] == task_id
quadrants = {item['id']: item for item in payload['quadrants']}
assert quadrants['urgent-important']['tasks'][0]['id'] == task_id