2026-05-09 21:47:33 +08:00

117 lines
4.0 KiB
Go

package task_test
import (
"context"
"testing"
"time"
"google.golang.org/grpc"
walletv1 "hyapp.local/api/proto/wallet/v1"
taskdomain "hyapp/services/activity-service/internal/domain/task"
taskservice "hyapp/services/activity-service/internal/service/task"
"hyapp/services/activity-service/internal/testutil/mysqltest"
)
// TestTaskEventQueryAndClaimFlow 覆盖任务定义、事件进度、App 查询、领奖和领奖幂等的主闭环。
func TestTaskEventQueryAndClaimFlow(t *testing.T) {
repository := mysqltest.NewRepository(t)
wallet := &fakeWalletClient{}
svc := taskservice.New(taskservice.Config{TaskTimezone: "Asia/Shanghai"}, repository, wallet)
now := time.Date(2026, 5, 9, 10, 0, 0, 0, time.FixedZone("CST", 8*60*60))
svc.SetClock(func() time.Time { return now })
definition, created, err := svc.UpsertTaskDefinition(context.Background(), taskdomain.DefinitionCommand{
TaskType: taskdomain.TypeDaily,
Category: "gift",
MetricType: "gift_spend_coin",
Title: "send gift",
TargetValue: 100,
TargetUnit: "coin",
RewardCoinAmount: 10,
Status: taskdomain.StatusActive,
SortOrder: 1,
OperatorAdminID: 90001,
})
if err != nil || !created {
t.Fatalf("create task definition failed: created=%v err=%v", created, err)
}
result, err := svc.ConsumeTaskEvent(context.Background(), taskdomain.Event{
EventID: "evt-gift-1",
EventType: "WalletGiftDebited",
SourceService: "wallet",
UserID: 10001,
MetricType: "gift_spend_coin",
Value: 120,
OccurredAtMS: now.UnixMilli(),
})
if err != nil {
t.Fatalf("ConsumeTaskEvent failed: %v", err)
}
if result.Status != taskdomain.EventStatusConsumed || result.MatchedTaskCount != 1 {
t.Fatalf("event result mismatch: %+v", result)
}
duplicate, err := svc.ConsumeTaskEvent(context.Background(), taskdomain.Event{
EventID: "evt-gift-1",
EventType: "WalletGiftDebited",
SourceService: "wallet",
UserID: 10001,
MetricType: "gift_spend_coin",
Value: 120,
OccurredAtMS: now.UnixMilli(),
})
if err != nil || duplicate.Status != taskdomain.EventStatusConsumed {
t.Fatalf("duplicate event should be acknowledged: result=%+v err=%v", duplicate, err)
}
list, err := svc.ListUserTasks(context.Background(), 10001)
if err != nil {
t.Fatalf("ListUserTasks failed: %v", err)
}
daily := list.Sections[0]
if len(daily.Items) != 1 || daily.Items[0].TaskID != definition.TaskID || daily.Items[0].ProgressValue != 120 || daily.Items[0].UserStatus != taskdomain.StatusCompleted || !daily.Items[0].Claimable {
t.Fatalf("daily task item mismatch: %+v", daily.Items)
}
claim, err := svc.ClaimTaskReward(context.Background(), taskdomain.ClaimCommand{
UserID: 10001,
TaskID: definition.TaskID,
TaskType: taskdomain.TypeDaily,
CycleKey: "2026-05-09",
CommandID: "cmd-claim-1",
})
if err != nil {
t.Fatalf("ClaimTaskReward failed: %v", err)
}
if claim.Status != taskdomain.ClaimStatusGranted || claim.WalletTransactionID != "wtx-task-1" || wallet.calls != 1 {
t.Fatalf("claim result mismatch: claim=%+v wallet_calls=%d", claim, wallet.calls)
}
again, err := svc.ClaimTaskReward(context.Background(), taskdomain.ClaimCommand{
UserID: 10001,
TaskID: definition.TaskID,
TaskType: taskdomain.TypeDaily,
CycleKey: "2026-05-09",
CommandID: "cmd-claim-1",
})
if err != nil {
t.Fatalf("ClaimTaskReward retry failed: %v", err)
}
if again.ClaimID != claim.ClaimID || wallet.calls != 1 {
t.Fatalf("claim idempotency mismatch: first=%+v second=%+v calls=%d", claim, again, wallet.calls)
}
}
type fakeWalletClient struct {
calls int
}
func (f *fakeWalletClient) CreditTaskReward(context.Context, *walletv1.CreditTaskRewardRequest, ...grpc.CallOption) (*walletv1.CreditTaskRewardResponse, error) {
f.calls++
return &walletv1.CreditTaskRewardResponse{
TransactionId: "wtx-task-1",
Amount: 10,
GrantedAtMs: 1778292000000,
Balance: &walletv1.AssetBalance{AssetType: "COIN", AvailableAmount: 10},
}, nil
}