- 新增 000071 数据库迁移:notifications 表支持 distribution_site_id 并自动回填历史分销订单通知 - 通知自动归属:在事务内根据订单或申诉自动关联当前分销站点 ID - 隔离查询与已读控制:列表、未读统计与批量已读均严格按站点隔离,防止主站与分站交叉泄漏 - 前台界面联动:消息中心通知 Tab 及移动端/PC端站内信页面展示分站专属标识与精准过滤
194 lines
5.6 KiB
Go
194 lines
5.6 KiB
Go
package notification
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"hfb_sys/backend/internal/database"
|
|
"hfb_sys/backend/internal/model"
|
|
)
|
|
|
|
func TestRepositoryUnreadCountAndMarkRead(t *testing.T) {
|
|
db := database.NewTestDB()
|
|
if err := db.AutoMigrate(&model.Notification{}); err != nil {
|
|
t.Fatalf("AutoMigrate() error = %v", err)
|
|
}
|
|
repo := NewRepository(db)
|
|
ctx := context.Background()
|
|
|
|
if err := db.Create(&model.Notification{
|
|
UserID: 10,
|
|
Type: "order",
|
|
Title: "订单通知",
|
|
Content: "请处理订单",
|
|
}).Error; err != nil {
|
|
t.Fatalf("Create() error = %v", err)
|
|
}
|
|
|
|
count, err := repo.UnreadCount(ctx, 10)
|
|
if err != nil {
|
|
t.Fatalf("UnreadCount() error = %v", err)
|
|
}
|
|
if count != 1 {
|
|
t.Fatalf("UnreadCount() = %d, want 1", count)
|
|
}
|
|
|
|
if err := repo.MarkRead(ctx, 10, 1); err != nil {
|
|
t.Fatalf("MarkRead() error = %v", err)
|
|
}
|
|
// 重复标记应保持幂等,不应误报不存在。
|
|
if err := repo.MarkRead(ctx, 10, 1); err != nil {
|
|
t.Fatalf("MarkRead() repeat error = %v", err)
|
|
}
|
|
|
|
count, err = repo.UnreadCount(ctx, 10)
|
|
if err != nil {
|
|
t.Fatalf("UnreadCount() after read error = %v", err)
|
|
}
|
|
if count != 0 {
|
|
t.Fatalf("UnreadCount() after read = %d, want 0", count)
|
|
}
|
|
}
|
|
|
|
func TestRepositoryMarkReadNotFound(t *testing.T) {
|
|
db := database.NewTestDB()
|
|
if err := db.AutoMigrate(&model.Notification{}); err != nil {
|
|
t.Fatalf("AutoMigrate() error = %v", err)
|
|
}
|
|
repo := NewRepository(db)
|
|
|
|
err := repo.MarkRead(context.Background(), 10, 99)
|
|
if !errors.Is(err, ErrNotificationNotFound) {
|
|
t.Fatalf("MarkRead() error = %v, want ErrNotificationNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestNotificationTenantIsolation(t *testing.T) {
|
|
db := database.NewTestDB()
|
|
if err := db.AutoMigrate(&model.Notification{}, &model.DistributionSite{}, &model.RentalOrder{}); err != nil {
|
|
t.Fatalf("AutoMigrate() error = %v", err)
|
|
}
|
|
repo := NewRepository(db)
|
|
ctx := context.Background()
|
|
|
|
site := model.DistributionSite{
|
|
ID: 101,
|
|
SiteCode: "branch_a",
|
|
SiteName: "分站A",
|
|
Status: "active",
|
|
}
|
|
if err := db.Create(&site).Error; err != nil {
|
|
t.Fatalf("create distribution site error: %v", err)
|
|
}
|
|
|
|
siteID := site.ID
|
|
// 1. 创建主站通知
|
|
mainNotif := model.Notification{
|
|
UserID: 20,
|
|
Type: "order",
|
|
Title: "主站订单通知",
|
|
Content: "这是主站的订单通知",
|
|
BizType: "order",
|
|
DistributionSiteID: nil,
|
|
}
|
|
if err := db.Create(&mainNotif).Error; err != nil {
|
|
t.Fatalf("create main notif error: %v", err)
|
|
}
|
|
|
|
// 2. 创建分站通知
|
|
branchNotif := model.Notification{
|
|
UserID: 20,
|
|
Type: "order",
|
|
Title: "分站A订单通知",
|
|
Content: "这是分站A的订单通知",
|
|
BizType: "order",
|
|
DistributionSiteID: &siteID,
|
|
}
|
|
if err := db.Create(&branchNotif).Error; err != nil {
|
|
t.Fatalf("create branch notif error: %v", err)
|
|
}
|
|
|
|
// 3. 验证未读数隔离
|
|
mainUnread, err := repo.UnreadCount(ctx, 20)
|
|
if err != nil || mainUnread != 1 {
|
|
t.Fatalf("mainUnread = %d, err = %v, want 1", mainUnread, err)
|
|
}
|
|
|
|
branchUnread, err := repo.UnreadCount(ctx, 20, "branch_a")
|
|
if err != nil || branchUnread != 1 {
|
|
t.Fatalf("branchUnread = %d, err = %v, want 1", branchUnread, err)
|
|
}
|
|
|
|
// 4. 验证列表查询隔离
|
|
mainList, err := repo.List(ctx, 20, 1, 10)
|
|
if err != nil || mainList.Total != 1 {
|
|
t.Fatalf("mainList total = %d, err = %v, want 1", mainList.Total, err)
|
|
}
|
|
items := mainList.Items.([]NotificationDTO)
|
|
if items[0].Title != "主站订单通知" {
|
|
t.Fatalf("mainList item title = %s, want 主站订单通知", items[0].Title)
|
|
}
|
|
|
|
branchList, err := repo.List(ctx, 20, 1, 10, "branch_a")
|
|
if err != nil || branchList.Total != 1 {
|
|
t.Fatalf("branchList total = %d, err = %v, want 1", branchList.Total, err)
|
|
}
|
|
branchItems := branchList.Items.([]NotificationDTO)
|
|
if branchItems[0].Title != "分站A订单通知" {
|
|
t.Fatalf("branchList item title = %s, want 分站A订单通知", branchItems[0].Title)
|
|
}
|
|
|
|
// 5. 跨站点标记单条已读阻断
|
|
err = repo.MarkRead(ctx, 20, mainNotif.ID, "branch_a")
|
|
if !errors.Is(err, ErrNotificationNotFound) {
|
|
t.Fatalf("cross-site MarkRead error = %v, want ErrNotificationNotFound", err)
|
|
}
|
|
|
|
// 6. 分站全部已读不影响主站
|
|
affected, err := repo.MarkAllRead(ctx, 20, "branch_a")
|
|
if err != nil || affected != 1 {
|
|
t.Fatalf("MarkAllRead affected = %d, err = %v, want 1", affected, err)
|
|
}
|
|
|
|
branchUnreadAfter, err := repo.UnreadCount(ctx, 20, "branch_a")
|
|
if err != nil || branchUnreadAfter != 0 {
|
|
t.Fatalf("branchUnreadAfter = %d, want 0", branchUnreadAfter)
|
|
}
|
|
|
|
mainUnreadAfter, err := repo.UnreadCount(ctx, 20)
|
|
if err != nil || mainUnreadAfter != 1 {
|
|
t.Fatalf("mainUnreadAfter = %d, want 1 (should remain unread)", mainUnreadAfter)
|
|
}
|
|
|
|
// 7. 测试 Append 自动关联订单 distribution_site_id
|
|
order := model.RentalOrder{
|
|
ID: 3001,
|
|
DistributionSiteID: &siteID,
|
|
OrderNo: "ORD3001",
|
|
}
|
|
if err := db.Create(&order).Error; err != nil {
|
|
t.Fatalf("create order error: %v", err)
|
|
}
|
|
orderID := order.ID
|
|
err = Append(db, Entry{
|
|
UserID: 20,
|
|
Type: "order",
|
|
Title: "订单自动关联分站",
|
|
Content: "自动读取订单 site ID",
|
|
BizType: "order",
|
|
BizID: &orderID,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Append with auto lookup error: %v", err)
|
|
}
|
|
|
|
var autoNotif model.Notification
|
|
if err := db.Where("biz_id = ? AND biz_type = 'order'", orderID).First(&autoNotif).Error; err != nil {
|
|
t.Fatalf("find autoNotif error: %v", err)
|
|
}
|
|
if autoNotif.DistributionSiteID == nil || *autoNotif.DistributionSiteID != siteID {
|
|
t.Fatalf("autoNotif.DistributionSiteID = %v, want %d", autoNotif.DistributionSiteID, siteID)
|
|
}
|
|
}
|