Files
live-hub-py/tests/test_audit_logs.py
T
yml2213 1ac6a13a2c fix(audit): 修复审计日志时区差并支持供应商单号搜索与展示
- 解决审计日志 8 小时时区差:后端输出带时区 UTC ISO 字符串,前端 formatTime 增加 UTC 自动解析换算
- 审计日志支持单号检索:list_audit_logs 关联 DouyuTask 匹配供应商单号、外部单号、UID、昵称等
- 列表摘要与详情抽屉优化:摘要直观展示供应商单号与外部单号,抽屉增加专用供应商订单列表并支持一键复制
- 增加测试用例覆盖时间格式与单号反向检索
2026-09-12 14:52:26 +08:00

244 lines
7.7 KiB
Python

"""充值审计日志的权限、查询和脱敏测试。"""
import json
import pytest
from fastapi import HTTPException
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from web.backend.database import Base
from web.backend.models import Account, AuditLog, DouyuTask, User
from web.backend.routers.audit import list_audit_logs, require_super_admin
from web.backend.services.audit_service import record_audit
class TestAuditLog:
def setup_method(self):
self.engine = create_engine("sqlite://")
Base.metadata.create_all(self.engine)
self.session = sessionmaker(bind=self.engine)()
self.admin = User(username="admin", password_hash="hash", role="super_admin")
self.operator = User(
username="operator",
password_hash="hash",
role="operation",
custom_permissions=["audit:view"],
)
self.session.add_all([self.admin, self.operator])
self.session.commit()
def teardown_method(self):
self.session.close()
Base.metadata.drop_all(self.engine)
self.engine.dispose()
def test_only_super_admin_can_read_audit_logs(self):
with pytest.raises(HTTPException) as context:
require_super_admin(self.operator)
assert context.value.status_code == 403
assert require_super_admin(self.admin) is self.admin
def test_list_audit_logs_supports_pagination_and_result_filter(self):
for index in range(3):
record_audit(
self.session,
self.admin,
action="recharge:yyb:payment",
target=f"yyb_task:{index}",
detail={"task_id": index},
success=index != 1,
)
self.session.commit()
result = list_audit_logs(
page=1,
page_size=1,
username=None,
action="recharge:yyb:payment",
keyword=None,
success=True,
start_time=None,
end_time=None,
db=self.session,
_=self.admin,
)
assert result["total"] == 2
assert len(result["items"]) == 1
assert result["items"][0]["success"]
def test_audit_detail_masks_sensitive_fields(self):
record_audit(
self.session,
self.admin,
action="recharge:yyb:payment",
detail={
"token": "token-value",
"nested": {"cookie": "cookie-value"},
"order_id": "order-1",
},
)
self.session.commit()
entry = self.session.query(AuditLog).one()
detail = json.loads(entry.detail)
assert detail["token"] == "[已脱敏]"
assert detail["nested"]["cookie"] == "[已脱敏]"
assert detail["order_id"] == "order-1"
def test_old_douyu_recharge_log_is_enriched_with_account_identity(self):
account = Account(
username="douyu-login",
password="password",
email="mail@example.com",
email_password="mail-password",
uid="10001",
nickname="斗鱼昵称",
)
self.session.add(account)
self.session.commit()
self.session.add(
DouyuTask(
batch_id="batch-old",
account_id=account.id,
task_type="create_gold_qr",
handbook_scope="elite",
created_by=self.admin.id,
)
)
self.session.add(
AuditLog(
user_id=self.admin.id,
username=self.admin.username,
action="recharge:douyu:create",
target="douyu_batch:batch-old",
detail="{}",
success=True,
)
)
self.session.commit()
result = list_audit_logs(
page=1,
page_size=50,
username=None,
action=None,
keyword=None,
success=None,
start_time=None,
end_time=None,
db=self.session,
_=self.admin,
)
detail = json.loads(result["items"][0]["detail"])
assert detail["recharge_accounts"][0]["username"] == "douyu-login"
assert detail["recharge_accounts"][0]["douyu_uid"] == "10001"
assert detail["recharge_channel"] == "wechat_qr"
assert detail["payment_method"] == "微信扫码支付"
def test_audit_log_created_at_has_utc_timezone_suffix(self):
record_audit(
self.session,
self.admin,
action="recharge:yyb:payment",
target="yyb_task:1",
detail={"test": 1},
success=True,
)
self.session.commit()
result = list_audit_logs(
page=1,
page_size=10,
username=None,
action=None,
keyword=None,
success=None,
start_time=None,
end_time=None,
db=self.session,
_=self.admin,
)
created_at_str = result["items"][0]["created_at"]
assert created_at_str is not None
assert "+00:00" in created_at_str or created_at_str.endswith("Z")
def test_search_audit_logs_by_supplier_order_id_and_out_order_id(self):
account = Account(
username="test-user",
password="password",
email="test@example.com",
email_password="pwd",
uid="473705204",
nickname="用户29160540",
)
self.session.add(account)
self.session.commit()
task = DouyuTask(
batch_id="01dee6f6a272",
account_id=account.id,
task_type="create_gold_qr",
status="success",
supplier_out_order_id="DYGF01DEE6F6A272T99458",
result={
"recharge_channel": "supplier_api",
"out_order_id": "DYGF01DEE6F6A272T99458",
"buy_num": 280,
"supplier_order": {
"order_id": "200000124398920260912141221",
"order_status": 2,
},
},
created_by=self.admin.id,
)
self.session.add(task)
self.session.add(
AuditLog(
user_id=self.admin.id,
username=self.admin.username,
action="recharge:douyu:create",
target="douyu_batch:01dee6f6a272",
detail=json.dumps({"batch_id": "01dee6f6a272"}),
success=True,
)
)
self.session.commit()
# 1. 按供应商单号搜索
result_by_order_id = list_audit_logs(
page=1,
page_size=10,
username=None,
action=None,
keyword="200000124398920260912141221",
success=None,
start_time=None,
end_time=None,
db=self.session,
_=self.admin,
)
assert result_by_order_id["total"] == 1
detail = json.loads(result_by_order_id["items"][0]["detail"])
assert "200000124398920260912141221" in detail.get("supplier_order_ids", [])
assert "DYGF01DEE6F6A272T99458" in detail.get("supplier_out_order_ids", [])
assert detail.get("payment_method") == "API 直充支付"
# 2. 按外部单号搜索
result_by_out_id = list_audit_logs(
page=1,
page_size=10,
username=None,
action=None,
keyword="DYGF01DEE6F6A272T99458",
success=None,
start_time=None,
end_time=None,
db=self.session,
_=self.admin,
)
assert result_by_out_id["total"] == 1