package taskcenter import ( "context" "testing" "chatapp3-golang/internal/common" "chatapp3-golang/internal/config" "chatapp3-golang/internal/integration" "chatapp3-golang/internal/model" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func newTestService(t *testing.T, java taskCenterJavaGateway) (*Service, *gorm.DB) { t.Helper() db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &model.TaskCenterConfig{}, &model.TaskCenterTaskConfig{}, &model.TaskCenterUserProgress{}, &model.TaskCenterEvent{}, &model.TaskCenterClaimRecord{}, ); err != nil { t.Fatalf("auto migrate: %v", err) } service := NewService(config.Config{ TaskCenter: config.TaskCenterConfig{ DefaultSysOrigin: "LIKEI", Timezone: "Asia/Riyadh", }, }, db, java) return service, db } func TestDailyTaskConfigEventListAndClaim(t *testing.T) { java := &taskCenterTestGateway{events: map[string]bool{}} service, _ := newTestService(t, java) ctx := context.Background() configResp, err := service.SaveConfig(ctx, SaveConfigRequest{ SysOrigin: "likei", Enabled: true, Timezone: "Asia/Riyadh", TaskCategory: TaskCategoryDaily, Tasks: []SaveTaskConfigInput{ { TaskCode: "daily_mic_duration", Enabled: true, TaskName: "使用麦克风 %d 分钟。", TaskDesc: "进度 / 目标", ConditionType: "mic_duration_seconds", TargetValue: 60, TargetUnit: "second", RewardGold: 1100, JumpType: "ROOM_RANDOM_WITH_MIC", SortOrder: 10, }, }, }) if err != nil { t.Fatalf("SaveConfig() error = %v", err) } if !configResp.Configured || !configResp.Enabled || len(configResp.Tasks) != 1 { t.Fatalf("SaveConfig() resp = %+v", configResp) } user := common.AuthUser{UserID: 1001, SysOrigin: "LIKEI"} tasks, err := service.ListTasks(ctx, user) if err != nil { t.Fatalf("ListTasks() error = %v", err) } if len(tasks) != 1 || tasks[0].TaskStatus != 0 || tasks[0].CompletedValue != 0 { t.Fatalf("initial tasks = %+v", tasks) } eventResp, err := service.ReceiveEvent(ctx, EventRequest{ SysOrigin: "LIKEI", EventID: "event-1", EventType: "MIC_DURATION_SECONDS", UserID: flexibleInt64(user.UserID), DeltaValue: 60, }) if err != nil { t.Fatalf("ReceiveEvent() error = %v", err) } if !eventResp.Processed || eventResp.Updated != 1 { t.Fatalf("ReceiveEvent() resp = %+v", eventResp) } tasks, err = service.ListTasks(ctx, user) if err != nil { t.Fatalf("ListTasks() after event error = %v", err) } if len(tasks) != 1 || tasks[0].TaskStatus != 1 || tasks[0].CompletedValue != 60 { t.Fatalf("completed tasks = %+v", tasks) } ok, err := service.Claim(ctx, user, tasks[0].TaskID) if err != nil { t.Fatalf("Claim() error = %v", err) } if !ok { t.Fatalf("Claim() = false, want true") } if len(java.changedEvents) != 1 { t.Fatalf("wallet changes = %d, want 1", len(java.changedEvents)) } if len(java.changedEvents[0]) > 50 { t.Fatalf("wallet event id length = %d, want <= 50: %s", len(java.changedEvents[0]), java.changedEvents[0]) } ok, err = service.Claim(ctx, user, tasks[0].TaskID) if err != nil { t.Fatalf("Claim() duplicate error = %v", err) } if !ok || len(java.changedEvents) != 1 { t.Fatalf("duplicate claim ok=%v changes=%d, want true/1", ok, len(java.changedEvents)) } } func TestExclusiveTaskConfigUsesLifetimeCycleAndHidesAfterClaim(t *testing.T) { java := &taskCenterTestGateway{events: map[string]bool{}} service, _ := newTestService(t, java) ctx := context.Background() _, err := service.SaveConfig(ctx, SaveConfigRequest{ SysOrigin: "LIKEI", Enabled: true, Timezone: "Asia/Riyadh", TaskCategory: TaskCategoryNewbie, Tasks: []SaveTaskConfigInput{ { TaskCode: "EXCLUSIVE_RECHARGE", Enabled: true, TaskName: "专属充值任务", ConditionType: "RECHARGE_GOLD", TargetValue: 10, TargetUnit: "gold", RewardGold: 100, }, }, }) if err != nil { t.Fatalf("SaveConfig() error = %v", err) } user := common.AuthUser{UserID: 2001, SysOrigin: "LIKEI"} tasks, err := service.ListTasks(ctx, user) if err != nil { t.Fatalf("ListTasks() error = %v", err) } if len(tasks) != 1 || tasks[0].TaskType != 1 { t.Fatalf("exclusive tasks = %+v, want one taskType=1", tasks) } eventResp, err := service.ReceiveEvent(ctx, EventRequest{ SysOrigin: "LIKEI", EventID: "recharge-1", EventType: "RECHARGE_GOLD", UserID: flexibleInt64(user.UserID), DeltaValue: 10, }) if err != nil { t.Fatalf("ReceiveEvent() error = %v", err) } if !eventResp.Processed || eventResp.Updated != 1 { t.Fatalf("ReceiveEvent() resp = %+v", eventResp) } ok, err := service.Claim(ctx, user, tasks[0].TaskID) if err != nil { t.Fatalf("Claim() error = %v", err) } if !ok { t.Fatalf("Claim() = false, want true") } tasks, err = service.ListTasks(ctx, user) if err != nil { t.Fatalf("ListTasks() after claim error = %v", err) } if len(tasks) != 0 { t.Fatalf("exclusive tasks after claim = %+v, want hidden", tasks) } } func TestTaskConfigRejectsRemovedConditionTypes(t *testing.T) { service, _ := newTestService(t, nil) for _, conditionType := range []string{"POST_MOOD_COUNT", "INTERACTIVE_GAME_COUNT", "CP_BIND_COUNT"} { t.Run(conditionType, func(t *testing.T) { _, err := service.SaveConfig(context.Background(), SaveConfigRequest{ SysOrigin: "LIKEI", Enabled: true, Timezone: "Asia/Riyadh", TaskCategory: TaskCategoryDaily, Tasks: []SaveTaskConfigInput{ { TaskCode: "REMOVED_" + conditionType, Enabled: true, TaskName: "Removed condition", ConditionType: conditionType, TargetValue: 1, TargetUnit: "count", RewardGold: 100, }, }, }) if err == nil { t.Fatalf("SaveConfig() error = nil, want invalid condition") } }) } } type taskCenterTestGateway struct { changedEvents []string events map[string]bool } func (g *taskCenterTestGateway) ExistsGoldEvent(_ context.Context, eventID string) (bool, error) { return g.events[eventID], nil } func (g *taskCenterTestGateway) ChangeGoldBalance(_ context.Context, cmd integration.GoldReceiptCommand) error { g.changedEvents = append(g.changedEvents, cmd.EventID) g.events[cmd.EventID] = true return nil }