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") } }