Files
DjangoAdmin_Tornado_EleVue/tests/unit/modules/test_job_scheduler.py
T
gitadmin fda0fe1a53
CI / lint (push) Canceled after 0s
CI / test (push) Canceled after 0s
签入版本
2026-09-16 10:08:07 +08:00

140 lines
4.9 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.
"""任务调度器测试:执行日志记录、Cron 校验与执行策略防重叠。"""
import types
import pytest
from modules.job import scheduler
from modules.job_log import service as job_log_service
def make_job(**overrides):
"""构造一个伪任务快照(无 DB),与调度器要求的 JobSnapshot 对齐。
默认 URL 用公网 IP 字面量(8.8.8.8):无需 DNS 即能通过 getaddrinfo,
且 is_global=True,符合调度器防 SSRF 的 URL 校验(不再使用 127.0.0.1)。
"""
defaults = dict(
id=1, job_name="测试任务", job_group="系统任务", job_trigger="cron",
cron_expression="*/5 * * * *", url="http://8.8.8.8/api/v1/health",
execute_policy=1, status=1,
)
defaults.update(overrides)
return scheduler._snapshot_job(types.SimpleNamespace(**defaults))
class FakeResponse:
"""支持 with 语法的伪 HTTP 响应。"""
status = 200
def __enter__(self):
return self
def __exit__(self, *exc):
return False
@pytest.fixture(autouse=True)
def _clean_running():
"""每个用例前后清空运行中集合,避免相互污染。"""
scheduler._running.clear()
yield
scheduler._running.clear()
def _capture(monkeypatch):
"""打桩 record_job_log,返回记录列表。"""
records = []
monkeypatch.setattr(job_log_service, "record_job_log", lambda **kw: records.append(kw))
return records
def test_execute_job_success_records_log(monkeypatch):
records = _capture(monkeypatch)
# execute_job 经 _no_redirect_opener.open 发起请求(禁重定向,防 SSRF 302 跳内网)
monkeypatch.setattr(scheduler._no_redirect_opener, "open", lambda url, timeout=None: FakeResponse())
scheduler.execute_job(make_job())
assert len(records) == 1
assert records[0]["status"] == 0
assert records[0]["job_name"] == "测试任务"
assert records[0]["job_message"] == "任务触发成功"
assert records[0]["consume_time"] >= 0
def test_execute_job_failure_records_exception(monkeypatch):
records = _capture(monkeypatch)
def _boom(url, timeout=None):
raise ConnectionError("connect timeout")
monkeypatch.setattr(scheduler._no_redirect_opener, "open", _boom)
scheduler.execute_job(make_job())
assert len(records) == 1
assert records[0]["status"] == 1
assert "connect timeout" in records[0]["exception_info"]
assert records[0]["job_message"] == "任务触发失败"
def test_overlap_policy_skip_while_running(monkeypatch):
"""策略 3-放弃执行:任务执行中时跳过本次触发,不调用任务 URL。"""
records = _capture(monkeypatch)
calls = {"urlopen": 0}
def _urlopen(url, timeout=None):
calls["urlopen"] += 1
return FakeResponse()
monkeypatch.setattr(scheduler._no_redirect_opener, "open", _urlopen)
job = make_job(execute_policy=3)
scheduler._running.add(job.id)
scheduler.execute_job(job)
assert calls["urlopen"] == 0
assert len(records) == 1
assert records[0]["status"] == 0
assert "放弃执行" in records[0]["job_message"]
# 跳过不等于释放运行标记:原执行仍占用,任务应保留在运行集合
assert job.id in scheduler._running
def test_overlap_policy_immediate_runs_in_parallel(monkeypatch):
"""策略 1-立即执行:即使任务执行中仍再次执行(并行)。"""
records = _capture(monkeypatch)
monkeypatch.setattr(scheduler._no_redirect_opener, "open", lambda url, timeout=None: FakeResponse())
job = make_job(execute_policy=1)
scheduler._running.add(job.id)
scheduler.execute_job(job)
assert len(records) == 1
assert records[0]["status"] == 0
assert job.id not in scheduler._running # 执行结束从运行集合移除
def test_validate_cron_expression():
assert scheduler.validate_cron_expression("*/5 * * * *") is None
assert scheduler.validate_cron_expression("") is not None
assert scheduler.validate_cron_expression("not-a-cron") is not None
def test_validate_job_url_blocks_ssrf():
"""防 SSRF:拒绝非 http/https 协议与内网/回环/云元数据地址。"""
assert scheduler.validate_job_url("http://8.8.8.8/ping") is True
assert scheduler.validate_job_url("http://127.0.0.1:8041/x") is False
assert scheduler.validate_job_url("http://192.168.1.1/x") is False
assert scheduler.validate_job_url("http://169.254.169.254/latest/meta-data/") is False
assert scheduler.validate_job_url("file:///etc/passwd") is False
assert scheduler.validate_job_url("ftp://example.com/x") is False
def test_no_redirect_handler_blocks_redirect():
"""禁重定向:公网 URL 302 跳内网的 SSRF 通道被切断。"""
handler = scheduler._NoRedirectHandler()
# redirect_request 返回 None -> urlopen 抛 HTTPError,不会继续请求跳转目标
assert handler.redirect_request(None, None, 302, "", None, "http://169.254.169.254/") is None