topfans/backend/services/taskService/mq/consumer_test.go
zerosaturation cc7794e0f4 test(task): consumer + daily_task repo/service + like_bet ON CONFLICT 修订
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>
2026-07-27 15:46:29 +08:00

183 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 初始化 loggerhandler 错误路径调 logger.Logger.Errornil 会 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 接口,
// 只模拟 ProcessTaskEventnewHandleTaskEvent 唯一调用的方法)。
// 其他方法返回零值,因为 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 errspec §5 让 Asynq 重试)
_ = err // err 已经是 processErrhandler 直接透传)
}
}
// ==================== 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")
}
}