- test_renewal_service.py: 14 个测试覆盖到期查询、文案生成、消息构建、续费检查流程 - test_ssl_service.py: 24 个测试覆盖子域名 CRUD、证书辅助函数、探测结果写入、全量刷新 - test_router_smoke.py: 16 个 HTTP smoke test 覆盖 assets/accounts/providers/stats 路由 全部 394 个测试通过
175 lines
6.6 KiB
Python
175 lines
6.6 KiB
Python
"""renewal_service 测试:续费检查与提醒消息生成
|
|
|
|
覆盖:
|
|
- get_expiring_assets: 到期资产查询(含已过期、排除已注销、按天数升序)
|
|
- day_text: 到期文案生成(已过期/今天/未来)
|
|
- build_renewal_message: 消息拼装(有过期+未过期混合、全部未过期、空列表)
|
|
- check_renewals: 完整流程(send=True 触发通知 / send=False 仅预览 / 默认阈值取配置)
|
|
|
|
全程 tmp_path + create_engine 临时库,mock 通知服务避免真实发送。
|
|
|
|
运行:.venv/bin/pytest tests/test_renewal_service.py -v
|
|
"""
|
|
|
|
from datetime import date, timedelta
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from sqlmodel import Session, SQLModel, create_engine
|
|
|
|
import app.models.asset # noqa: F401
|
|
import app.models.credential # noqa: F401
|
|
import app.models.monitor # noqa: F401
|
|
import app.models.provider # noqa: F401
|
|
import app.models.ssl # noqa: F401
|
|
from app.models.asset import Asset, AssetStatus, AssetType
|
|
from app.services import renewal_service
|
|
|
|
|
|
@pytest.fixture()
|
|
def engine(tmp_path):
|
|
eng = create_engine(
|
|
f"sqlite:///{tmp_path / 'assets.db'}", connect_args={"check_same_thread": False}
|
|
)
|
|
SQLModel.metadata.create_all(eng)
|
|
yield eng
|
|
eng.dispose()
|
|
|
|
|
|
def _seed(engine, name, *, expiry, status=AssetStatus.ACTIVE, provider="vultr"):
|
|
with Session(engine) as s:
|
|
s.add(Asset(
|
|
name=name, asset_type=AssetType.VPS, provider=provider,
|
|
expiry_date=expiry, status=status,
|
|
))
|
|
s.commit()
|
|
|
|
|
|
# ----------------------------- day_text ----------------------------- #
|
|
def test_day_text_overdue():
|
|
assert renewal_service.day_text(-5) == "已过期 5 天"
|
|
assert renewal_service.day_text(-1) == "已过期 1 天"
|
|
|
|
|
|
def test_day_text_today():
|
|
assert renewal_service.day_text(0) == "今天到期"
|
|
|
|
|
|
def test_day_text_future():
|
|
assert renewal_service.day_text(1) == "1 天后"
|
|
assert renewal_service.day_text(30) == "30 天后"
|
|
|
|
|
|
# ----------------------------- get_expiring_assets ----------------------------- #
|
|
def test_get_expiring_assets_empty(engine):
|
|
with Session(engine) as s:
|
|
result = renewal_service.get_expiring_assets(s, threshold_days=30)
|
|
assert result == []
|
|
|
|
|
|
def test_get_expiring_assets_includes_overdue_and_sorted(engine):
|
|
"""已过期资产也纳入提醒(不能因过了到期日就消失),按剩余天数升序"""
|
|
today = date.today()
|
|
_seed(engine, "overdue", expiry=today - timedelta(days=3))
|
|
_seed(engine, "soon", expiry=today + timedelta(days=5))
|
|
_seed(engine, "far", expiry=today + timedelta(days=90))
|
|
with Session(engine) as s:
|
|
result = renewal_service.get_expiring_assets(s, threshold_days=30)
|
|
names = [a.name for a, _ in result]
|
|
assert names == ["overdue", "soon"]
|
|
assert result[0][1] < result[1][1] # -3 < 5
|
|
|
|
|
|
def test_get_expiring_assets_excludes_cancelled(engine):
|
|
"""已注销资产不参与续费提醒"""
|
|
today = date.today()
|
|
_seed(engine, "active", expiry=today + timedelta(days=5))
|
|
_seed(engine, "cancelled", expiry=today + timedelta(days=5), status=AssetStatus.CANCELLED)
|
|
with Session(engine) as s:
|
|
result = renewal_service.get_expiring_assets(s, threshold_days=30)
|
|
names = [a.name for a, _ in result]
|
|
assert names == ["active"]
|
|
|
|
|
|
def test_get_expiring_assets_no_expiry_date_skipped(engine):
|
|
"""未填到期日的资产不出现在到期列表"""
|
|
_seed(engine, "no-expiry", expiry=None)
|
|
# _seed 不支持 None expiry,直接插入
|
|
with Session(engine) as s:
|
|
s.add(Asset(name="no-expiry", asset_type=AssetType.VPS, provider="vultr"))
|
|
s.commit()
|
|
result = renewal_service.get_expiring_assets(s, threshold_days=30)
|
|
assert result == []
|
|
|
|
|
|
# ----------------------------- build_renewal_message ----------------------------- #
|
|
def test_build_message_empty():
|
|
assert renewal_service.build_renewal_message([], 30) == ""
|
|
|
|
|
|
def test_build_message_with_overdue():
|
|
"""混合已过期和即将到期时,消息头部区分两类"""
|
|
today = date.today()
|
|
a1 = Asset(name="vps-1", provider="vultr", expiry_date=today - timedelta(days=2))
|
|
a2 = Asset(name="vps-2", provider="vultr", expiry_date=today + timedelta(days=10))
|
|
expiring = [(a1, -2), (a2, 10)]
|
|
msg = renewal_service.build_renewal_message(expiring, threshold_days=30)
|
|
assert "已过期 1 项" in msg
|
|
assert "30 天内到期 1 项" in msg
|
|
assert "vps-1" in msg
|
|
assert "vps-2" in msg
|
|
assert "已过期 2 天" in msg
|
|
assert "10 天后" in msg
|
|
|
|
|
|
def test_build_message_no_overdue():
|
|
"""全部未过期时头部只显示常规文案"""
|
|
today = date.today()
|
|
a1 = Asset(name="vps-1", provider="vultr", expiry_date=today + timedelta(days=5))
|
|
expiring = [(a1, 5)]
|
|
msg = renewal_service.build_renewal_message(expiring, threshold_days=30)
|
|
assert "已过期" not in msg
|
|
assert "共 1 项" in msg
|
|
|
|
|
|
# ----------------------------- check_renewals ----------------------------- #
|
|
def test_check_renewals_no_send(engine):
|
|
"""send=False 时不触发通知,仅返回预览"""
|
|
today = date.today()
|
|
_seed(engine, "soon", expiry=today + timedelta(days=5))
|
|
with Session(engine) as s:
|
|
result = renewal_service.check_renewals(s, threshold_days=30, send=False)
|
|
assert result["expiring_count"] == 1
|
|
assert result["threshold_days"] == 30
|
|
assert result["notifications"] == []
|
|
assert result["expiring"][0]["name"] == "soon"
|
|
assert result["expiring"][0]["days"] == 5
|
|
|
|
|
|
def test_check_renewals_send_with_no_expiring(engine):
|
|
"""无到期资产时不发送通知"""
|
|
with Session(engine) as s:
|
|
result = renewal_service.check_renewals(s, threshold_days=30, send=True)
|
|
assert result["expiring_count"] == 0
|
|
assert result["message"] == ""
|
|
assert result["notifications"] == []
|
|
|
|
|
|
def test_check_renewals_send_dispatches_notification(engine):
|
|
"""send=True 且有到期资产时调用通知服务"""
|
|
today = date.today()
|
|
_seed(engine, "soon", expiry=today + timedelta(days=3))
|
|
with Session(engine) as s:
|
|
with patch.object(renewal_service.notification_service, "notify", return_value=[{"ok": True}]) as mock_notify:
|
|
result = renewal_service.check_renewals(s, threshold_days=30, send=True)
|
|
mock_notify.assert_called_once()
|
|
assert result["expiring_count"] == 1
|
|
assert result["notifications"] == [{"ok": True}]
|
|
|
|
|
|
def test_check_renewals_default_threshold_from_config(engine):
|
|
"""threshold_days=None 时从 settings 读取默认值"""
|
|
with Session(engine) as s:
|
|
result = renewal_service.check_renewals(s, send=False)
|
|
assert result["threshold_days"] == renewal_service.settings.RENEWAL_THRESHOLD_DAYS
|