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 := database.AutoMigrateIfMissing(db, &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 := database.AutoMigrateIfMissing(db, &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 := database.AutoMigrateIfMissing(db, &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) } }