geo/tests/test_scheduler.py

123 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import uuid
from datetime import datetime, timezone, timedelta
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from app.models.query import Query
from app.models.user import User
from app.services.auth import hash_password
from app.workers.citation_engine import CitationEngine
from app.workers.scheduler import QueryScheduler
# ---------------------------------------------------------------------------
# 1. 调度器启动 / 关闭
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_scheduler_start_stop():
scheduler = QueryScheduler()
scheduler.engine = AsyncMock()
scheduler.start()
# Verify the scheduled job was added
job = scheduler.scheduler.get_job("check_queries")
assert job is not None
assert job.name == "检查并执行到期的查询任务"
await scheduler.shutdown()
# Verify engine.close was awaited
scheduler.engine.close.assert_awaited_once()
# ---------------------------------------------------------------------------
# 2. 查询任务筛选:只选择 status=active 且 next_query_at <= now 的查询
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_scheduler_query_filtering(test_session):
# Create a user first
user = User(
email="sched@test.com",
password_hash=hash_password("pass"),
name="Scheduler",
)
test_session.add(user)
await test_session.commit()
await test_session.refresh(user)
now = datetime.now(timezone.utc)
# q1: active and overdue -> should be picked
q1 = Query(
user_id=user.id,
keyword="overdue",
target_brand="B1",
status="active",
next_query_at=now - timedelta(hours=1),
)
# q2: active but in the future -> should NOT be picked
q2 = Query(
user_id=user.id,
keyword="future",
target_brand="B2",
status="active",
next_query_at=now + timedelta(days=1),
)
# q3: paused and overdue -> should NOT be picked
q3 = Query(
user_id=user.id,
keyword="paused",
target_brand="B3",
status="paused",
next_query_at=now - timedelta(hours=1),
)
test_session.add_all([q1, q2, q3])
await test_session.commit()
scheduler = QueryScheduler()
scheduler.engine = AsyncMock()
# Mock AsyncSessionLocal so scheduler uses our test session
mock_local = MagicMock()
mock_local.return_value.__aenter__ = AsyncMock(return_value=test_session)
mock_local.return_value.__aexit__ = AsyncMock(return_value=False)
with patch("app.workers.scheduler.AsyncSessionLocal", mock_local):
await scheduler.check_and_execute_queries()
# execute_query should be called exactly once (for q1)
scheduler.engine.execute_query.assert_called_once()
called_query = scheduler.engine.execute_query.call_args[0][0]
assert called_query.keyword == "overdue"
# ---------------------------------------------------------------------------
# 3. 频率计算daily 和 weekly 的 next_query_at 正确计算
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_scheduler_frequency_calculation_daily():
engine = CitationEngine()
now = datetime.utcnow()
result = engine._calculate_next_query_at("daily")
expected = now + timedelta(days=1)
assert abs((result - expected).total_seconds()) < 5
@pytest.mark.asyncio
async def test_scheduler_frequency_calculation_weekly():
engine = CitationEngine()
now = datetime.utcnow()
result = engine._calculate_next_query_at("weekly")
expected = now + timedelta(days=7)
assert abs((result - expected).total_seconds()) < 5
@pytest.mark.asyncio
async def test_scheduler_frequency_calculation_default():
engine = CitationEngine()
now = datetime.utcnow()
result = engine._calculate_next_query_at(None)
expected = now + timedelta(days=7)
assert abs((result - expected).total_seconds()) < 5