package weekstar import ( "context" "encoding/json" "errors" "strings" "testing" "time" "chatapp3-golang/internal/config" "chatapp3-golang/internal/integration" "chatapp3-golang/internal/model" "gorm.io/driver/sqlite" "gorm.io/gorm" ) type weekStarTestGateway struct{} func (g weekStarTestGateway) GetRewardGroupDetail(_ context.Context, groupID int64) (integration.RewardGroupDetail, error) { if groupID < 2004471533988167125 || groupID > 2004471533988167127 { return integration.RewardGroupDetail{ID: integration.Int64Value(groupID)}, nil } return integration.RewardGroupDetail{ ID: integration.Int64Value(groupID), Name: "Top Reward", RewardConfigList: []integration.RewardGroupItem{ { ID: 2004471533988167001, Type: "PROPS", Name: "Frame", Content: "1001", Quantity: 7, Cover: "https://example.com/frame.png", }, }, }, nil } func (g weekStarTestGateway) GetUserProfile(context.Context, int64) (integration.UserProfile, error) { return integration.UserProfile{}, nil } type unavailableRewardDetailGateway struct { weekStarTestGateway } func (g unavailableRewardDetailGateway) GetRewardGroupDetail(context.Context, int64) (integration.RewardGroupDetail, error) { return integration.RewardGroupDetail{}, errors.New("java reward detail unavailable") } func newTestWeekStarService(t *testing.T) (*WeekStarService, *gorm.DB) { return newTestWeekStarServiceWithGateway(t, weekStarTestGateway{}) } func newTestWeekStarServiceWithGateway(t *testing.T, gateway weekStarGateway) (*WeekStarService, *gorm.DB) { t.Helper() dbName := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name()) db, err := gorm.Open(sqlite.Open("file:"+dbName+"?mode=memory&cache=shared"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &model.WeekStarActivityConfig{}, &model.WeekStarActivityGiftConfig{}, &model.WeekStarActivityRewardConfig{}, ); err != nil { t.Fatalf("auto migrate: %v", err) } service := NewWeekStarService(config.Config{ WeekStar: config.WeekStarConfig{ DefaultSysOrigin: "LIKEI", Timezone: "Asia/Riyadh", }, }, db, nil, gateway) return service, db } func TestSaveWeekStarConfigRequestPreservesStringIDs(t *testing.T) { const longID int64 = 2004471533988167125 var req SaveWeekStarConfigRequest if err := json.Unmarshal([]byte(`{ "id": "2004471533988167125", "giftConfigs": [{"giftId": "2004471533988167125"}], "rewardConfigs": [{"rank": 1, "rewardGroupId": "2004471533988167125"}] }`), &req); err != nil { t.Fatalf("Unmarshal() error = %v", err) } if req.ID.Int64() != longID { t.Fatalf("req.ID = %d, want %d", req.ID.Int64(), longID) } if req.GiftConfigs[0].GiftID.Int64() != longID { t.Fatalf("giftId = %d, want %d", req.GiftConfigs[0].GiftID.Int64(), longID) } if req.RewardConfigs[0].RewardGroupID.Int64() != longID { t.Fatalf("rewardGroupId = %d, want %d", req.RewardConfigs[0].RewardGroupID.Int64(), longID) } } func TestWeekStarConfigResponseSerializesLongIDsAsStrings(t *testing.T) { const longID int64 = 2004471533988167125 data, err := json.Marshal(WeekStarConfigResponse{ ID: longID, GiftConfigs: []WeekStarGiftConfigPayload{ {GiftID: longID}, }, RewardConfigs: []WeekStarRewardConfigPayload{ {Rank: 1, RewardGroupID: longID}, }, }) if err != nil { t.Fatalf("Marshal() error = %v", err) } jsonText := string(data) for _, want := range []string{ `"id":"2004471533988167125"`, `"giftId":"2004471533988167125"`, `"rewardGroupId":"2004471533988167125"`, } { if !strings.Contains(jsonText, want) { t.Fatalf("json = %s, want contains %s", jsonText, want) } } } func TestSaveConfigAllowsCurrentWeekWhenNoActiveConfigExists(t *testing.T) { service, _ := newTestWeekStarService(t) now := time.Now().In(service.location) cycleStart, cycleEnd := service.cycleBounds(now) resp, err := service.SaveConfig(context.Background(), SaveWeekStarConfigRequest{ SysOrigin: "LIKEI", Enabled: true, StartAt: formatDateTime(cycleStart), EndAt: formatDateTime(cycleEnd), Timezone: "Asia/Riyadh", GiftConfigs: []WeekStarGiftConfigInput{ {GiftID: flexibleInt64(101), GiftName: "Gift 1", Sort: 1}, {GiftID: flexibleInt64(102), GiftName: "Gift 2", Sort: 2}, {GiftID: flexibleInt64(103), GiftName: "Gift 3", Sort: 3}, }, RewardConfigs: []WeekStarRewardConfigInput{ {Rank: 1, RewardGroupID: flexibleInt64(2004471533988167125), RewardGroupName: "Top1"}, {Rank: 2, RewardGroupID: flexibleInt64(2004471533988167126), RewardGroupName: "Top2"}, {Rank: 3, RewardGroupID: flexibleInt64(2004471533988167127), RewardGroupName: "Top3"}, }, }) if err != nil { t.Fatalf("SaveConfig() error = %v", err) } if resp == nil || !resp.Configured || resp.Status != weekStarActivityStatusOngoing { t.Fatalf("SaveConfig() response = %#v, want ongoing configured response", resp) } if got := resp.RewardConfigs[0].RewardGroupID; got != 2004471533988167125 { t.Fatalf("rewardGroupId = %d, want exact long id", got) } if len(resp.RewardConfigs[0].Items) != 1 { t.Fatalf("reward items length = %d, want 1", len(resp.RewardConfigs[0].Items)) } } func TestSaveConfigAllowsRewardGroupWhenDetailUnavailable(t *testing.T) { service, _ := newTestWeekStarServiceWithGateway(t, unavailableRewardDetailGateway{}) now := time.Now().In(service.location) cycleStart, cycleEnd := service.cycleBounds(now) resp, err := service.SaveConfig(context.Background(), SaveWeekStarConfigRequest{ SysOrigin: "LIKEI", Enabled: true, StartAt: formatDateTime(cycleStart), EndAt: formatDateTime(cycleEnd), Timezone: "Asia/Riyadh", GiftConfigs: []WeekStarGiftConfigInput{ {GiftID: flexibleInt64(101), GiftName: "Gift 1", Sort: 1}, {GiftID: flexibleInt64(102), GiftName: "Gift 2", Sort: 2}, {GiftID: flexibleInt64(103), GiftName: "Gift 3", Sort: 3}, }, RewardConfigs: []WeekStarRewardConfigInput{ {Rank: 1, RewardGroupID: flexibleInt64(2004471533988167125), RewardGroupName: "Top1 Fallback"}, {Rank: 2, RewardGroupID: flexibleInt64(2004471533988167126), RewardGroupName: "Top2 Fallback"}, {Rank: 3, RewardGroupID: flexibleInt64(2004471533988167127), RewardGroupName: "Top3 Fallback"}, }, }) if err != nil { t.Fatalf("SaveConfig() error = %v", err) } if resp == nil || len(resp.RewardConfigs) != 3 { t.Fatalf("SaveConfig() response = %#v, want 3 reward configs", resp) } if got := resp.RewardConfigs[0].RewardGroupName; got != "Top1 Fallback" { t.Fatalf("rewardGroupName = %q, want fallback name", got) } if len(resp.RewardConfigs[0].Items) != 0 { t.Fatalf("reward items length = %d, want 0 when detail is unavailable", len(resp.RewardConfigs[0].Items)) } } func TestSaveConfigRejectsCurrentWeekWhenActiveConfigExists(t *testing.T) { service, db := newTestWeekStarService(t) now := time.Now().In(service.location) cycleStart, cycleEnd := service.cycleBounds(now) if err := db.Create(&model.WeekStarActivityConfig{ ID: 1, SysOrigin: "LIKEI", Enabled: true, StartAt: service.toStorageWallClock(cycleStart), EndAt: service.toStorageWallClock(cycleEnd), Timezone: "Asia/Riyadh", CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed active config: %v", err) } _, err := service.SaveConfig(context.Background(), SaveWeekStarConfigRequest{ SysOrigin: "LIKEI", Enabled: true, StartAt: formatDateTime(cycleStart), EndAt: formatDateTime(cycleEnd), Timezone: "Asia/Riyadh", GiftConfigs: []WeekStarGiftConfigInput{ {GiftID: flexibleInt64(101), GiftName: "Gift 1", Sort: 1}, {GiftID: flexibleInt64(102), GiftName: "Gift 2", Sort: 2}, {GiftID: flexibleInt64(103), GiftName: "Gift 3", Sort: 3}, }, RewardConfigs: []WeekStarRewardConfigInput{ {Rank: 1, RewardGroupID: flexibleInt64(2004471533988167125), RewardGroupName: "Top1"}, {Rank: 2, RewardGroupID: flexibleInt64(2004471533988167126), RewardGroupName: "Top2"}, {Rank: 3, RewardGroupID: flexibleInt64(2004471533988167127), RewardGroupName: "Top3"}, }, }) if err == nil { t.Fatalf("SaveConfig() error = nil, want overlap error") } }