package service import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "affiliate_dash/internal/model" "affiliate_dash/internal/pkg/timeutil" ) func TestAssignTokenForOrder_SetsUsingAtAndReleaseClears(t *testing.T) { db := newRockTestDB(t) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch { case strings.HasPrefix(r.URL.Path, "/api/redeem/"): _ = json.NewEncoder(w).Encode(map[string]interface{}{ "id": 151153, "redeem_code": "CODE-USING-1", "bind_url": "https://gp.qq.com/cp/mock_bind", }) case strings.HasSuffix(r.URL.Path, "/refresh"): _ = json.NewEncoder(w).Encode(map[string]interface{}{}) default: w.WriteHeader(http.StatusNotFound) } })) defer server.Close() svc := NewRockService(db, NewRockClient(server.URL)) setRockTestBearer(t, svc, "bearer-test") token := model.RockToken{ UpstreamTokenID: "88001", Token: "jwt-using", RedeemCode: "CODE-USING-1", Color: model.RockColorBlack, Status: model.RockStatusAvailable, } if err := db.Create(&token).Error; err != nil { t.Fatalf("创建可用 Token 失败: %v", err) } assigned, _, err := svc.AssignTokenForOrder(context.Background(), "ORDER-USING-1", "12345678") if err != nil { t.Fatalf("分配 Token 失败: %v", err) } if assigned.Status != model.RockStatusUsing { t.Fatalf("分配后状态应为 using,实际 %s", assigned.Status) } if assigned.UsingAt == nil { t.Fatalf("分配后 using_at 必须写入") } var stored model.RockToken if err := db.First(&stored, assigned.ID).Error; err != nil { t.Fatalf("重新读取 Token 失败: %v", err) } if stored.UsingAt == nil { t.Fatalf("数据库中 using_at 未写入") } if err := svc.ReleaseUnboundUsingToken(context.Background(), assigned.ID); err != nil { t.Fatalf("释放未绑定 Token 失败: %v", err) } var released model.RockToken if err := db.First(&released, assigned.ID).Error; err != nil { t.Fatalf("读取释放后的 Token 失败: %v", err) } if released.Status != model.RockStatusAvailable { t.Fatalf("释放后状态应为 available,实际 %s", released.Status) } if released.UsingAt != nil { t.Fatalf("释放回库存后 using_at 必须清空,实际 %v", released.UsingAt) } } func TestImportPackageDetail_SetsUsingAtFromClaimedAt(t *testing.T) { db := newRockTestDB(t) svc := NewRockService(db, NewRockClient("https://example.com")) var detail RockPackageDetailResponse raw := `{ "package": {"id": 9001, "share_code": "share-using"}, "tokens": [ {"id": 30001, "token": "t1", "redeem_code": "c1", "color": "black", "status": "using", "created_at": "2026-10-27 23:31:59"} ] }` if err := json.Unmarshal([]byte(raw), &detail); err != nil { t.Fatalf("解析包详情失败: %v", err) } inserted, _, _, err := svc.importPackageDetail(context.Background(), &detail, nil) if err != nil { t.Fatalf("导入包详情失败: %v", err) } if inserted != 1 { t.Fatalf("期望新增 1 条,实际 %d", inserted) } var row model.RockToken if err := db.Where("upstream_token_id = ?", "30001").First(&row).Error; err != nil { t.Fatalf("查询导入 Token 失败: %v", err) } if row.Status != model.RockStatusUsing { t.Fatalf("期望状态 using,实际 %s", row.Status) } if row.UsingAt == nil { t.Fatalf("上游状态为 using 时必须写入 using_at") } expected := time.Date(2026, 10, 27, 23, 31, 59, 0, timeutil.Location()) if !row.UsingAt.Equal(expected) { t.Fatalf("using_at 应取上游 claimed_at,实际 %v", row.UsingAt) } } func TestUpdateConfig_UsingWarningDaysValidation(t *testing.T) { db := newRockTestDB(t) svc := NewRockService(db, NewRockClient("https://example.com")) ctx := context.Background() cfg, err := svc.GetConfig(ctx) if err != nil { t.Fatalf("获取配置失败: %v", err) } if cfg.UsingWarnDays != 3 || cfg.UsingDangerDays != 5 { t.Fatalf("默认阈值应为 3/5,实际 %d/%d", cfg.UsingWarnDays, cfg.UsingDangerDays) } warnSix := 6 if _, err := svc.UpdateConfig(ctx, UpdateRockConfigReq{UsingWarnDays: &warnSix}); err == nil { t.Fatalf("橙色预警 6 天大于默认红色 5 天时必须报错") } warnFour, dangerFour := 4, 4 if _, err := svc.UpdateConfig(ctx, UpdateRockConfigReq{UsingWarnDays: &warnFour, UsingDangerDays: &dangerFour}); err == nil { t.Fatalf("红色预警不大于橙色预警时必须报错") } warnZero := 0 if _, err := svc.UpdateConfig(ctx, UpdateRockConfigReq{UsingWarnDays: &warnZero}); err == nil { t.Fatalf("预警天数小于 1 时必须报错") } warnTwo, dangerFourAgain := 2, 4 updated, err := svc.UpdateConfig(ctx, UpdateRockConfigReq{UsingWarnDays: &warnTwo, UsingDangerDays: &dangerFourAgain}) if err != nil { t.Fatalf("合法阈值保存失败: %v", err) } if updated.UsingWarnDays != 2 || updated.UsingDangerDays != 4 { t.Fatalf("阈值未保存,实际 %d/%d", updated.UsingWarnDays, updated.UsingDangerDays) } }