Files

182 lines
6.3 KiB
Python

"""消息通知与智能预警集成测试。"""
import pytest
from datetime import date, timedelta
from decimal import Decimal
from model_bakery import baker
from rest_framework.test import APIClient
from rest_framework_simplejwt.tokens import RefreshToken
from apps.catalog.models import Product
from apps.inventory.models import Warehouse, Stock
from apps.partner.models import Customer
from apps.finance.models import Receivable
from apps.notify.models import Notification, AlertRule
from apps.notify import services as notify_services
@pytest.fixture
def auth_client(db, user, tenant):
client = APIClient()
refresh = RefreshToken.for_user(user)
client.credentials(
HTTP_AUTHORIZATION=f"Bearer {refresh.access_token}",
HTTP_X_TENANT_ID=tenant.code,
)
return client
@pytest.mark.django_db
def test_send_notification(tenant, user):
msg = notify_services.send_notification(
tenant=tenant,
title="系统通知",
content="欢迎使用 dealerhub 经销商中枢",
recipient=user,
category="info",
link="/dashboard/",
)
assert msg.title == "系统通知"
assert msg.is_read is False
assert msg.recipient == user
@pytest.mark.django_db
def test_inventory_low_alert_trigger(tenant):
notify_services.init_default_alert_rules(tenant)
wh = baker.make(Warehouse, tenant=tenant, code="WH_ALERT", name="警戒仓")
p_low = baker.make(Product, tenant=tenant, code="P_LOW", name="缺货饮料")
p_ok = baker.make(Product, tenant=tenant, code="P_OK", name="充足大米")
# 低于阈值 (5 <= 10)
baker.make(Stock, tenant=tenant, warehouse=wh, product=p_low, on_hand=Decimal("5.00"), locked=Decimal("0"))
# 充足库存 (100 > 10)
baker.make(Stock, tenant=tenant, warehouse=wh, product=p_ok, on_hand=Decimal("100.00"), locked=Decimal("0"))
triggered = notify_services.check_inventory_low_alerts(tenant)
assert triggered == 1
note = Notification.objects.filter(tenant=tenant, category="warning").first()
assert note is not None
assert "缺货饮料" in note.title
assert note.is_read is False
@pytest.mark.django_db
def test_receivable_overdue_alert_trigger(tenant):
notify_services.init_default_alert_rules(tenant)
cust = baker.make(Customer, tenant=tenant, code="C_DEBT", name="欠款客户")
# 30天前到期,至今未还
overdue_date = date.today() - timedelta(days=30)
baker.make(
Receivable,
tenant=tenant,
customer=cust,
bill_no="RC-OVERDUE-01",
bill_date=overdue_date - timedelta(days=30),
due_date=overdue_date,
total_amount=Decimal("10000.00"),
paid_amount=Decimal("0.00"),
status="open",
)
triggered = notify_services.check_receivable_overdue_alerts(tenant)
assert triggered == 1
note = Notification.objects.filter(tenant=tenant, category="warning", extra_data__receivable_id__isnull=False).first()
assert note is not None
assert "欠款客户" in note.title
assert "催款提醒" in note.title
@pytest.mark.django_db
def test_notify_api_endpoints(auth_client, tenant):
# 触发全量预警扫描
r = auth_client.post("/api/v1/notify/rules/run-checks/")
assert r.status_code == 200
assert r.json()["ok"] is True
# 写入一条通知并标记已读
note = notify_services.send_notification(tenant, "测试待办", "内容", category="audit")
r2 = auth_client.post(f"/api/v1/notify/messages/{note.id}/mark-read/")
assert r2.status_code == 200
assert r2.json()["is_read"] is True
# 全部标记已读
r3 = auth_client.post("/api/v1/notify/messages/mark-all-read/")
assert r3.status_code == 200
assert r3.json()["ok"] is True
@pytest.mark.django_db
def test_notification_list_is_scoped_to_broadcast_and_current_recipient(
auth_client, tenant, other_tenant, user, django_user_model
):
other_user = django_user_model.objects.create_user(
username="notification-owner", password="password12345"
)
visible_broadcast = notify_services.send_notification(
tenant, "租户广播", "所有人可见"
)
visible_personal = notify_services.send_notification(
tenant, "我的通知", "当前用户可见", recipient=user
)
hidden_personal = notify_services.send_notification(
tenant, "别人的通知", "其他用户不可见", recipient=other_user
)
hidden_tenant = notify_services.send_notification(
other_tenant, "其他租户广播", "当前租户不可见"
)
response = auth_client.get("/api/v1/notify/messages/")
assert response.status_code == 200
returned_ids = {item["id"] for item in response.json()["results"]}
assert returned_ids == {visible_broadcast.id, visible_personal.id}
assert hidden_personal.id not in returned_ids
assert hidden_tenant.id not in returned_ids
@pytest.mark.django_db
def test_mark_read_rejects_notification_owned_by_another_user(
auth_client, tenant, user, django_user_model
):
other_user = django_user_model.objects.create_user(
username="different-notification-owner", password="password12345"
)
note = notify_services.send_notification(
tenant, "私有通知", "只属于其他用户", recipient=other_user
)
response = auth_client.post(f"/api/v1/notify/messages/{note.id}/mark-read/")
assert response.status_code == 404
note.refresh_from_db()
assert note.is_read is False
assert note.read_at is None
@pytest.mark.django_db
def test_mark_all_read_only_updates_broadcast_and_current_user(
auth_client, tenant, user, django_user_model
):
other_user = django_user_model.objects.create_user(
username="mark-all-other-owner", password="password12345"
)
broadcast = notify_services.send_notification(tenant, "广播", "所有人")
mine = notify_services.send_notification(tenant, "我的", "当前用户", recipient=user)
theirs = notify_services.send_notification(tenant, "别人的", "其他用户", recipient=other_user)
response = auth_client.post("/api/v1/notify/messages/mark-all-read/")
assert response.status_code == 200
assert response.json()["updated_count"] == 2
broadcast.refresh_from_db()
mine.refresh_from_db()
theirs.refresh_from_db()
assert broadcast.is_read is True
assert mine.is_read is True
assert theirs.is_read is False