mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-07-17 11:32:36 +08:00
Refine transfer history batch actions
This commit is contained in:
@@ -30,6 +30,28 @@ def test_dashboard_system_info_returns_runtime_environment(monkeypatch):
|
||||
assert result.version == "v2.13.16"
|
||||
|
||||
|
||||
def test_memory_usage_returns_used_cached_and_available(monkeypatch):
|
||||
"""内存统计应拆分已使用、缓存和可用容量,并返回已使用百分比。"""
|
||||
|
||||
class FakeMemory:
|
||||
"""提供固定系统内存值的桩。"""
|
||||
|
||||
total = 16 * 1024**3
|
||||
used = 5 * 1024**3
|
||||
cached = 3 * 1024**3
|
||||
buffers = 512 * 1024**2
|
||||
|
||||
monkeypatch.setattr(system_module.psutil, "virtual_memory", FakeMemory)
|
||||
|
||||
result = SystemUtils.memory_usage()
|
||||
|
||||
assert result.total == 16 * 1024**3
|
||||
assert result.used == 5 * 1024**3
|
||||
assert result.cached == int(3.5 * 1024**3)
|
||||
assert result.available == int(7.5 * 1024**3)
|
||||
assert result.usage == 31.25
|
||||
|
||||
|
||||
def test_monthly_media_statistics_counts_successful_unique_media():
|
||||
"""本月新增统计应只计算成功记录,并按媒体去重。"""
|
||||
month = system_module.time.strftime("%Y-%m-", system_module.time.localtime())
|
||||
|
||||
154
tests/test_scheduler_progress.py
Normal file
154
tests/test_scheduler_progress.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import asyncio
|
||||
import threading
|
||||
from uuid import uuid4
|
||||
|
||||
from app.core.config import global_vars
|
||||
from app.scheduler import Scheduler
|
||||
|
||||
|
||||
def _build_scheduler(job_id, func):
|
||||
"""构造不启动 APScheduler 的定时服务测试对象。"""
|
||||
scheduler = object.__new__(Scheduler)
|
||||
scheduler._lock = threading.RLock()
|
||||
scheduler._jobs = {
|
||||
job_id: {
|
||||
"name": "测试定时服务",
|
||||
"provider_name": "测试",
|
||||
"func": func,
|
||||
"running": False,
|
||||
}
|
||||
}
|
||||
return scheduler
|
||||
|
||||
|
||||
def test_scheduler_records_live_and_completed_progress():
|
||||
"""定时服务应在执行中更新进度,并在成功后收敛为 100%。"""
|
||||
job_id = f"test-success-{uuid4()}"
|
||||
snapshots = []
|
||||
|
||||
def task(progress_callback):
|
||||
"""上报一次中间进度。"""
|
||||
progress_callback(value=42, text="正在处理", data={"finished": 2})
|
||||
snapshots.append(scheduler.get_progress(job_id))
|
||||
|
||||
scheduler = _build_scheduler(job_id, task)
|
||||
|
||||
scheduler.start(job_id)
|
||||
|
||||
assert snapshots[0].enable is True
|
||||
assert snapshots[0].value == 42
|
||||
assert snapshots[0].status == "running"
|
||||
assert snapshots[0].data["finished"] == 2
|
||||
progress = scheduler.get_progress(job_id)
|
||||
assert progress.enable is False
|
||||
assert progress.value == 100
|
||||
assert progress.status == "success"
|
||||
assert progress.success is True
|
||||
assert progress.started_at
|
||||
assert progress.finished_at
|
||||
|
||||
|
||||
def test_scheduler_failure_preserves_last_progress(monkeypatch):
|
||||
"""定时服务异常时应保留失败位置,而不是伪装成 100% 完成。"""
|
||||
job_id = f"test-failure-{uuid4()}"
|
||||
|
||||
def task(progress_callback):
|
||||
"""上报进度后抛出异常。"""
|
||||
progress_callback(value=37, text="处理失败")
|
||||
raise RuntimeError("预期失败")
|
||||
|
||||
scheduler = _build_scheduler(job_id, task)
|
||||
monkeypatch.setattr(
|
||||
scheduler,
|
||||
"_Scheduler__handle_job_error",
|
||||
lambda **kwargs: None,
|
||||
)
|
||||
|
||||
scheduler.start(job_id)
|
||||
|
||||
progress = scheduler.get_progress(job_id)
|
||||
assert progress.enable is False
|
||||
assert progress.value == 37
|
||||
assert progress.status == "failed"
|
||||
assert progress.success is False
|
||||
assert progress.error == "预期失败"
|
||||
|
||||
|
||||
def test_scheduler_treats_standard_failure_result_as_failed():
|
||||
"""返回 `(False, message)` 的定时服务应记录为业务失败。"""
|
||||
job_id = f"test-result-failure-{uuid4()}"
|
||||
|
||||
def task(progress_callback):
|
||||
"""返回标准失败结果。"""
|
||||
progress_callback(value=55, text="业务校验失败")
|
||||
return False, "业务失败"
|
||||
|
||||
scheduler = _build_scheduler(job_id, task)
|
||||
|
||||
scheduler.start(job_id)
|
||||
|
||||
progress = scheduler.get_progress(job_id)
|
||||
assert progress.value == 55
|
||||
assert progress.status == "failed"
|
||||
assert progress.error == "业务失败"
|
||||
|
||||
|
||||
def test_scheduler_runs_async_job_without_running_global_loop(monkeypatch):
|
||||
"""全局事件循环未运行时,异步定时服务仍应正常执行并收敛进度。"""
|
||||
job_id = f"test-async-{uuid4()}"
|
||||
|
||||
async def task(progress_callback):
|
||||
"""上报异步任务进度。"""
|
||||
progress_callback(value=65, text="异步处理中")
|
||||
|
||||
scheduler = _build_scheduler(job_id, task)
|
||||
target_loop = asyncio.new_event_loop()
|
||||
monkeypatch.setattr(global_vars, "CURRENT_EVENT_LOOP", target_loop)
|
||||
|
||||
try:
|
||||
scheduler.start(job_id)
|
||||
finally:
|
||||
target_loop.close()
|
||||
|
||||
progress = scheduler.get_progress(job_id)
|
||||
assert progress.enable is False
|
||||
assert progress.value == 100
|
||||
assert progress.status == "success"
|
||||
|
||||
|
||||
def test_scheduler_runs_async_job_from_current_event_loop(monkeypatch):
|
||||
"""在异步入口中手动触发定时服务时,不应嵌套调用 `asyncio.run`。"""
|
||||
job_id = f"test-current-loop-{uuid4()}"
|
||||
|
||||
async def task(progress_callback):
|
||||
"""在当前事件循环中上报进度。"""
|
||||
progress_callback(value=75, text="当前循环处理中")
|
||||
|
||||
async def run_task():
|
||||
"""从已运行的事件循环启动定时服务。"""
|
||||
scheduler.start(job_id)
|
||||
await asyncio.sleep(0)
|
||||
|
||||
scheduler = _build_scheduler(job_id, task)
|
||||
target_loop = asyncio.new_event_loop()
|
||||
monkeypatch.setattr(global_vars, "CURRENT_EVENT_LOOP", target_loop)
|
||||
|
||||
try:
|
||||
asyncio.run(run_task())
|
||||
finally:
|
||||
target_loop.close()
|
||||
|
||||
progress = scheduler.get_progress(job_id)
|
||||
assert progress.enable is False
|
||||
assert progress.value == 100
|
||||
assert progress.status == "success"
|
||||
|
||||
|
||||
def test_scheduler_returns_none_for_unknown_job():
|
||||
"""未注册且无历史进度的定时服务应返回空。"""
|
||||
job_id = f"test-unknown-{uuid4()}"
|
||||
scheduler = object.__new__(Scheduler)
|
||||
scheduler._lock = threading.RLock()
|
||||
scheduler._jobs = {}
|
||||
|
||||
assert scheduler.get_progress(job_id) is None
|
||||
Reference in New Issue
Block a user