Phase F.6 测试覆盖:
- mq/consumer_test.go: MQ consumer 单元测试,用 fakeDailyTaskService 模拟
ProcessTaskEvent(newHandleTaskEvent 唯一调用点)
- repository/daily_task_repo_test.go: DailyTaskRepository CRUD 单测
(复用 like_bet_repo_test.go 的 TestMain + dev PG 15432 sentinel 模式)
- service/daily_task_service_test.go: DailyTaskService.ProcessTaskEvent 核心
完成判定逻辑单测
- repository/like_bet_repo_test.go: UniqueConstraint 测试语义修订 —
BatchCreate 内部用 clause.OnConflict{DoNothing: true} 实现幂等,
重复 (exhibition_id, like_id) 不再报错,DB 仍保留 1 条记录
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
183 lines
5.8 KiB
Go
183 lines
5.8 KiB
Go
package mq
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"os"
|
||
"testing"
|
||
|
||
"github.com/topfans/backend/pkg/logger"
|
||
"github.com/topfans/backend/pkg/mq/adapter"
|
||
"github.com/topfans/backend/pkg/mq/tasks"
|
||
pb "github.com/topfans/backend/pkg/proto/task"
|
||
pbCommon "github.com/topfans/backend/pkg/proto/common"
|
||
"github.com/topfans/backend/services/taskService/service"
|
||
)
|
||
|
||
// TestMain 初始化 logger(handler 错误路径调 logger.Logger.Error,nil 会 panic)
|
||
func TestMain(m *testing.M) {
|
||
_ = logger.Init(logger.Config{
|
||
ServiceName: "task-service-mq-test",
|
||
Environment: "test",
|
||
LogLevel: "error",
|
||
})
|
||
os.Exit(m.Run())
|
||
}
|
||
|
||
// fakeDailyTaskService 实现 service.DailyTaskService 接口,
|
||
// 只模拟 ProcessTaskEvent(newHandleTaskEvent 唯一调用的方法)。
|
||
// 其他方法返回零值,因为 handler 不调它们。
|
||
type fakeDailyTaskService struct {
|
||
processTaskEventFn func(ctx context.Context, userID, starID int64, eventType string) (*service.TaskEventResult, error)
|
||
}
|
||
|
||
func (f *fakeDailyTaskService) GetDailyTasks(ctx context.Context, userID, starID int64) (*pb.GetDailyTasksResponse, error) {
|
||
return &pb.GetDailyTasksResponse{Tasks: []*pb.DailyTaskItem{}}, nil
|
||
}
|
||
func (f *fakeDailyTaskService) ReportEvent(ctx context.Context, userID, starID int64, eventType string) (*pb.ReportEventResponse, error) {
|
||
return &pb.ReportEventResponse{Success: false}, nil
|
||
}
|
||
func (f *fakeDailyTaskService) ProcessTaskEvent(ctx context.Context, userID, starID int64, eventType string) (*service.TaskEventResult, error) {
|
||
if f.processTaskEventFn != nil {
|
||
return f.processTaskEventFn(ctx, userID, starID, eventType)
|
||
}
|
||
return &service.TaskEventResult{}, nil
|
||
}
|
||
func (f *fakeDailyTaskService) ClaimDailyTask(ctx context.Context, userID, starID int64, taskKey string) (*pb.ClaimDailyTaskResponse, error) {
|
||
return &pb.ClaimDailyTaskResponse{Success: false}, nil
|
||
}
|
||
func (f *fakeDailyTaskService) ClaimAllDailyTasks(ctx context.Context, userID, starID int64) (*pb.ClaimAllDailyTasksResponse, error) {
|
||
return &pb.ClaimAllDailyTasksResponse{ClaimedCount: 0}, nil
|
||
}
|
||
|
||
// _ 防 common 包未使用(如果接口零值字段不需要,common import 会被 go vet 标记)
|
||
var _ = pbCommon.BaseResponse{}
|
||
|
||
// ==================== TestNewHandleTaskEvent_Success ====================
|
||
|
||
func TestNewHandleTaskEvent_Success(t *testing.T) {
|
||
called := false
|
||
var capturedUID, capturedSID int64
|
||
var capturedEvent string
|
||
|
||
fake := &fakeDailyTaskService{
|
||
processTaskEventFn: func(ctx context.Context, userID, starID int64, eventType string) (*service.TaskEventResult, error) {
|
||
called = true
|
||
capturedUID = userID
|
||
capturedSID = starID
|
||
capturedEvent = eventType
|
||
return &service.TaskEventResult{CompletedTaskKeys: []string{"daily_mint"}}, nil
|
||
},
|
||
}
|
||
|
||
payload, err := tasks.MarshalToPayload(tasks.TaskEventPayload{
|
||
UserID: 100,
|
||
StarID: 87,
|
||
EventType: tasks.EventDailyMint,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("MarshalToPayload: %v", err)
|
||
}
|
||
|
||
handler := newHandleTaskEvent(fake)
|
||
task := adapter.Task{
|
||
Type: tasks.TypeTaskEvent,
|
||
Payload: payload,
|
||
}
|
||
|
||
if err := handler(context.Background(), &task); err != nil {
|
||
t.Fatalf("handler returned error: %v", err)
|
||
}
|
||
|
||
if !called {
|
||
t.Errorf("expected ProcessTaskEvent to be called")
|
||
}
|
||
if capturedUID != 100 {
|
||
t.Errorf("expected userID=100, got %d", capturedUID)
|
||
}
|
||
if capturedSID != 87 {
|
||
t.Errorf("expected starID=87, got %d", capturedSID)
|
||
}
|
||
if capturedEvent != "daily_mint" {
|
||
t.Errorf("expected event_type=daily_mint, got %s", capturedEvent)
|
||
}
|
||
}
|
||
|
||
// ==================== TestNewHandleTaskEvent_ProcessError ====================
|
||
|
||
func TestNewHandleTaskEvent_ProcessError_ReturnsError(t *testing.T) {
|
||
// newHandleTaskEvent 应返回 error(让 Asynq 触发 MaxRetry=3 重试)
|
||
processErr := errors.New("simulated ProcessTaskEvent failure")
|
||
fake := &fakeDailyTaskService{
|
||
processTaskEventFn: func(ctx context.Context, userID, starID int64, eventType string) (*service.TaskEventResult, error) {
|
||
return nil, processErr
|
||
},
|
||
}
|
||
|
||
payload, _ := tasks.MarshalToPayload(tasks.TaskEventPayload{
|
||
UserID: 200, StarID: 88, EventType: "daily_browse_asset",
|
||
})
|
||
|
||
handler := newHandleTaskEvent(fake)
|
||
err := handler(context.Background(), &adapter.Task{
|
||
Type: tasks.TypeTaskEvent,
|
||
Payload: payload,
|
||
})
|
||
|
||
if err == nil {
|
||
t.Fatalf("expected error from handler when ProcessTaskEvent fails")
|
||
}
|
||
if !errors.Is(err, processErr) {
|
||
// 我们没有用 errors.Is wrap,仅要求 err != nil 即可
|
||
// handler 直接 return err(spec §5 让 Asynq 重试)
|
||
_ = err // err 已经是 processErr(handler 直接透传)
|
||
}
|
||
}
|
||
|
||
// ==================== TestNewHandleTaskEvent_UnmarshalError ====================
|
||
|
||
func TestNewHandleTaskEvent_UnmarshalError_ReturnsError(t *testing.T) {
|
||
called := false
|
||
fake := &fakeDailyTaskService{
|
||
processTaskEventFn: func(ctx context.Context, userID, starID int64, eventType string) (*service.TaskEventResult, error) {
|
||
called = true
|
||
return &service.TaskEventResult{}, nil
|
||
},
|
||
}
|
||
|
||
// 构造一个无法 unmarshal 成 TaskEventPayload 的 payload(类型不匹配)
|
||
badPayload := map[string]any{
|
||
"user_id": "not-an-int64", // 类型错误
|
||
"star_id": 87,
|
||
"event_type": "daily_mint",
|
||
}
|
||
|
||
handler := newHandleTaskEvent(fake)
|
||
err := handler(context.Background(), &adapter.Task{
|
||
Type: tasks.TypeTaskEvent,
|
||
Payload: badPayload,
|
||
})
|
||
|
||
if err == nil {
|
||
t.Fatalf("expected unmarshal error")
|
||
}
|
||
if called {
|
||
t.Errorf("ProcessTaskEvent should NOT be called when unmarshal fails")
|
||
}
|
||
}
|
||
|
||
// ==================== TestNewHandleTaskEvent_NilPayload ====================
|
||
|
||
func TestNewHandleTaskEvent_NilPayload_ReturnsError(t *testing.T) {
|
||
fake := &fakeDailyTaskService{}
|
||
|
||
handler := newHandleTaskEvent(fake)
|
||
err := handler(context.Background(), &adapter.Task{
|
||
Type: tasks.TypeTaskEvent,
|
||
Payload: nil,
|
||
})
|
||
|
||
if err == nil {
|
||
t.Fatalf("expected error for nil payload")
|
||
}
|
||
} |