feat(sms): 新增节点短信提醒(审核未通过/被租用需辅助/上架未成交)
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/database"
|
||||
"hfb_sys/backend/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func newSmsTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db := database.NewTestDB()
|
||||
if err := database.AutoMigrateIfMissing(db, &model.SmsMessage{}); err != nil {
|
||||
t.Fatalf("AutoMigrate() error = %v", err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func TestEnqueueResolvesPhoneAndDeduplicates(t *testing.T) {
|
||||
db := newSmsTestDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
user := model.User{Phone: "13800001111", Nickname: "号主"}
|
||||
if err := db.Create(&user).Error; err != nil {
|
||||
t.Fatalf("创建用户失败: %v", err)
|
||||
}
|
||||
|
||||
entry := Entry{
|
||||
UserID: user.ID,
|
||||
Node: NodeListingReviewRejected,
|
||||
BizType: "listing",
|
||||
BizID: 9001,
|
||||
}
|
||||
if inserted, err := Enqueue(db.WithContext(ctx), entry); err != nil {
|
||||
t.Fatalf("Enqueue() error = %v", err)
|
||||
} else if !inserted {
|
||||
t.Fatal("Enqueue() 首次入队应插入新记录")
|
||||
}
|
||||
// 同一业务对象重复入队应被唯一索引静默忽略。
|
||||
if inserted, err := Enqueue(db.WithContext(ctx), entry); err != nil {
|
||||
t.Fatalf("Enqueue() 重复调用 error = %v", err)
|
||||
} else if inserted {
|
||||
t.Fatal("Enqueue() 重复入队不应插入新记录")
|
||||
}
|
||||
|
||||
var rows []model.SmsMessage
|
||||
if err := db.Where("node = ? AND biz_id = ?", NodeListingReviewRejected, 9001).Find(&rows).Error; err != nil {
|
||||
t.Fatalf("查询短信记录失败: %v", err)
|
||||
}
|
||||
if len(rows) != 1 {
|
||||
t.Fatalf("记录数 = %d, want 1(唯一索引应保证幂等)", len(rows))
|
||||
}
|
||||
if rows[0].Phone != user.Phone {
|
||||
t.Fatalf("phone = %q, want %q(应从用户表补齐手机号)", rows[0].Phone, user.Phone)
|
||||
}
|
||||
if rows[0].Status != model.SmsStatusPending {
|
||||
t.Fatalf("status = %q, want %q", rows[0].Status, model.SmsStatusPending)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnqueueIgnoresUnknownNodeAndMissingUser(t *testing.T) {
|
||||
db := newSmsTestDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
if _, err := Enqueue(db.WithContext(ctx), Entry{UserID: 1, Node: "not_registered", BizType: "listing", BizID: 1}); err != nil {
|
||||
t.Fatalf("未登记节点不应报错: %v", err)
|
||||
}
|
||||
if _, err := Enqueue(db.WithContext(ctx), Entry{UserID: 0, Node: NodeOrderHandoffAssist, BizType: "order", BizID: 1}); err != nil {
|
||||
t.Fatalf("缺少用户不应报错: %v", err)
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := db.Model(&model.SmsMessage{}).Count(&count).Error; err != nil {
|
||||
t.Fatalf("统计失败: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("记录数 = %d, want 0", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryStatusTransitions(t *testing.T) {
|
||||
db := newSmsTestDB(t)
|
||||
ctx := context.Background()
|
||||
repo := NewRepository(db)
|
||||
|
||||
row := model.SmsMessage{
|
||||
UserID: 11,
|
||||
Phone: "13800002222",
|
||||
Node: NodeOrderHandoffAssist,
|
||||
BizType: "order",
|
||||
BizID: 7001,
|
||||
Status: model.SmsStatusPending,
|
||||
}
|
||||
if err := db.Create(&row).Error; err != nil {
|
||||
t.Fatalf("创建记录失败: %v", err)
|
||||
}
|
||||
|
||||
// 首次拉取应命中,重试退避窗口内的记录应被排除。
|
||||
pending, err := repo.ListPending(ctx, 10, time.Now().Add(-time.Hour))
|
||||
if err != nil {
|
||||
t.Fatalf("ListPending() error = %v", err)
|
||||
}
|
||||
if len(pending) != 1 {
|
||||
t.Fatalf("ListPending() = %d 条, want 1", len(pending))
|
||||
}
|
||||
|
||||
if err := repo.MarkRetry(ctx, row.ID, 1, "渠道限流"); err != nil {
|
||||
t.Fatalf("MarkRetry() error = %v", err)
|
||||
}
|
||||
var retried model.SmsMessage
|
||||
if err := db.First(&retried, row.ID).Error; err != nil {
|
||||
t.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
if retried.Status != model.SmsStatusPending || retried.RetryCount != 1 || retried.LastError != "渠道限流" {
|
||||
t.Fatalf("重试后记录 = %+v", retried)
|
||||
}
|
||||
// 退避窗口内不应再次被拉取。
|
||||
pending, err = repo.ListPending(ctx, 10, time.Now().Add(-time.Hour))
|
||||
if err != nil {
|
||||
t.Fatalf("ListPending() error = %v", err)
|
||||
}
|
||||
if len(pending) != 0 {
|
||||
t.Fatalf("退避窗口内 ListPending() = %d 条, want 0", len(pending))
|
||||
}
|
||||
// 退避窗口外应重新进入待发送队列。
|
||||
pending, err = repo.ListPending(ctx, 10, time.Now().Add(time.Hour))
|
||||
if err != nil {
|
||||
t.Fatalf("ListPending() error = %v", err)
|
||||
}
|
||||
if len(pending) != 1 {
|
||||
t.Fatalf("退避窗口外 ListPending() = %d 条, want 1", len(pending))
|
||||
}
|
||||
|
||||
sentAt := time.Now()
|
||||
if err := repo.MarkSent(ctx, row.ID, "SMS_512540945", "大锤商行", sentAt); err != nil {
|
||||
t.Fatalf("MarkSent() error = %v", err)
|
||||
}
|
||||
var sent model.SmsMessage
|
||||
if err := db.First(&sent, row.ID).Error; err != nil {
|
||||
t.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
if sent.Status != model.SmsStatusSent || sent.TemplateCode != "SMS_512540945" || sent.SignName != "大锤商行" || sent.SentAt == nil {
|
||||
t.Fatalf("发送成功后记录 = %+v", sent)
|
||||
}
|
||||
|
||||
if err := repo.MarkSkipped(ctx, row.ID, "节点短信总开关未开启"); err != nil {
|
||||
t.Fatalf("MarkSkipped() error = %v", err)
|
||||
}
|
||||
var skipped model.SmsMessage
|
||||
if err := db.First(&skipped, row.ID).Error; err != nil {
|
||||
t.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
if skipped.Status != model.SmsStatusSkipped || skipped.SkipReason != "节点短信总开关未开启" {
|
||||
t.Fatalf("跳过记录 = %+v", skipped)
|
||||
}
|
||||
|
||||
if err := repo.MarkFailed(ctx, row.ID, 3, "渠道异常"); err != nil {
|
||||
t.Fatalf("MarkFailed() error = %v", err)
|
||||
}
|
||||
var failed model.SmsMessage
|
||||
if err := db.First(&failed, row.ID).Error; err != nil {
|
||||
t.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
if failed.Status != model.SmsStatusFailed || failed.RetryCount != 3 {
|
||||
t.Fatalf("失败记录 = %+v", failed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnqueueKeepsSiteID(t *testing.T) {
|
||||
db := newSmsTestDB(t)
|
||||
ctx := context.Background()
|
||||
siteID := uint64(77)
|
||||
|
||||
user := model.User{Phone: "13800003333"}
|
||||
if err := db.Create(&user).Error; err != nil {
|
||||
t.Fatalf("创建用户失败: %v", err)
|
||||
}
|
||||
if _, err := Enqueue(db.WithContext(ctx), Entry{
|
||||
UserID: user.ID,
|
||||
Node: NodeListingStalePriceSuggestion,
|
||||
BizType: "listing",
|
||||
BizID: 5001,
|
||||
DistributionSiteID: &siteID,
|
||||
}); err != nil {
|
||||
t.Fatalf("Enqueue() error = %v", err)
|
||||
}
|
||||
|
||||
var row model.SmsMessage
|
||||
if err := db.Where("biz_id = ?", 5001).First(&row).Error; err != nil {
|
||||
t.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
if row.DistributionSiteID == nil || *row.DistributionSiteID != siteID {
|
||||
t.Fatalf("distribution_site_id = %v, want %d", row.DistributionSiteID, siteID)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user