117 lines
4.0 KiB
Go
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(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
|
|
}
|