滞回标志按 app+pool 单行存储且由每个用户钉住的组内配置推进; 两组三元组不同(尤其 control 关/treatment 开的灰度形态)会互踩: control 逐抽清掉 treatment 的标志,阈值不同的组按对方状态加权付奖。 加权特性的灰度必须走常规发布,不能走实验。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
111 lines
4.2 KiB
Go
111 lines
4.2 KiB
Go
package databi
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
"time"
|
||
|
||
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
|
||
)
|
||
|
||
type fakeLuckyGiftDailyStatsSource struct {
|
||
lastReq *luckygiftv1.ListLuckyGiftDailyStatsRequest
|
||
stats []*luckygiftv1.LuckyGiftDailyStat
|
||
}
|
||
|
||
func (f *fakeLuckyGiftDailyStatsSource) ListLuckyGiftDailyStats(_ context.Context, req *luckygiftv1.ListLuckyGiftDailyStatsRequest) (*luckygiftv1.ListLuckyGiftDailyStatsResponse, error) {
|
||
f.lastReq = req
|
||
return &luckygiftv1.ListLuckyGiftDailyStatsResponse{Stats: f.stats}, nil
|
||
}
|
||
|
||
func TestRequirementsLuckyGiftV3Gating(t *testing.T) {
|
||
for _, tc := range []struct {
|
||
section string
|
||
includes bool
|
||
only bool
|
||
}{
|
||
{"", true, false},
|
||
{"all", true, false},
|
||
{"lucky_gift_v3", true, true},
|
||
{" Lucky_Gift_V3 ", true, true},
|
||
{"revenue", false, false},
|
||
{"game", false, false},
|
||
} {
|
||
if got := requirementsIncludesLuckyGiftV3(tc.section); got != tc.includes {
|
||
t.Fatalf("includes(%q)=%v, want %v", tc.section, got, tc.includes)
|
||
}
|
||
if got := requirementsOnlyLuckyGiftV3(tc.section); got != tc.only {
|
||
t.Fatalf("only(%q)=%v, want %v", tc.section, got, tc.only)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 回归:section 组装——按北京日零填充整个窗口、比例按合计重算、v3 策略与区域过滤透传到 RPC。
|
||
func TestLuckyGiftV3Section(t *testing.T) {
|
||
source := &fakeLuckyGiftDailyStatsSource{stats: []*luckygiftv1.LuckyGiftDailyStat{
|
||
{StatDay: "2026-07-11", DrawCount: 4, TurnoverCoin: 8_000, PayoutCoin: 6_000, BaseRewardCoin: 5_500},
|
||
}}
|
||
service := &Service{luckyGiftStats: source, luckyGiftTimeout: time.Second}
|
||
|
||
// 北京 2026-07-10 00:00 ~ 2026-07-12 00:00(排他)= 两个北京日。
|
||
startMS := time.Date(2026, 7, 9, 16, 0, 0, 0, time.UTC).UnixMilli()
|
||
endMS := time.Date(2026, 7, 11, 16, 0, 0, 0, time.UTC).UnixMilli()
|
||
section, err := service.luckyGiftV3Section(context.Background(), "lalu", RequirementsQuery{StartMS: startMS, EndMS: endMS}, []int64{7, 8})
|
||
if err != nil {
|
||
t.Fatalf("build section: %v", err)
|
||
}
|
||
|
||
if source.lastReq.GetStrategyVersion() != "dynamic_v3" || source.lastReq.GetMeta().GetAppCode() != "lalu" {
|
||
t.Fatalf("rpc request mismatch: %+v", source.lastReq)
|
||
}
|
||
if regions := source.lastReq.GetRegionIds(); len(regions) != 2 || regions[0] != 7 || regions[1] != 8 {
|
||
t.Fatalf("rpc region ids mismatch: %v", regions)
|
||
}
|
||
if section["key"] != luckyGiftV3SectionKey {
|
||
t.Fatalf("section key mismatch: %v", section["key"])
|
||
}
|
||
|
||
rows, ok := section["daily_series"].([]map[string]any)
|
||
if !ok || len(rows) != 2 {
|
||
t.Fatalf("daily_series rows=%v, want 2 zero-filled days", section["daily_series"])
|
||
}
|
||
if rows[0]["stat_day"] != "2026-07-10" || rows[0]["draw_count"] != float64(0) {
|
||
t.Fatalf("zero-filled day mismatch: %+v", rows[0])
|
||
}
|
||
if rows[0]["payout_rate"] != nil {
|
||
t.Fatalf("zero turnover day must render nil ratio, got %v", rows[0]["payout_rate"])
|
||
}
|
||
if rows[1]["stat_day"] != "2026-07-11" || rows[1]["turnover_coin"] != float64(8_000) || rows[1]["profit_coin"] != float64(2_000) {
|
||
t.Fatalf("data day mismatch: %+v", rows[1])
|
||
}
|
||
if rate := rows[1]["payout_rate"].(float64); rate != 0.75 {
|
||
t.Fatalf("payout_rate=%v, want 0.75", rate)
|
||
}
|
||
|
||
total, ok := section["total"].(map[string]any)
|
||
if !ok || total["draw_count"] != float64(4) || total["profit_coin"] != float64(2_000) {
|
||
t.Fatalf("total mismatch: %+v", total)
|
||
}
|
||
average, ok := section["average"].(map[string]any)
|
||
if !ok || average["draw_count"] != float64(2) || average["turnover_coin"] != float64(4_000) {
|
||
t.Fatalf("average mismatch: %+v", average)
|
||
}
|
||
// 平均值行的比例 = 合计比例(4000/… 与 6000/8000 相同),不是逐日比例算术平均。
|
||
if rate := average["payout_rate"].(float64); rate != 0.75 {
|
||
t.Fatalf("average payout_rate=%v, want 0.75", rate)
|
||
}
|
||
}
|
||
|
||
func TestLuckyGiftV3StatDaysCrossesBeijingMidnight(t *testing.T) {
|
||
// UTC 2026-07-10 15:00 ~ 17:00 跨北京 7-10/7-11 两日。
|
||
startMS := time.Date(2026, 7, 10, 15, 0, 0, 0, time.UTC).UnixMilli()
|
||
endMS := time.Date(2026, 7, 10, 17, 0, 0, 0, time.UTC).UnixMilli()
|
||
days := luckyGiftV3StatDays(startMS, endMS)
|
||
if len(days) != 2 || days[0] != "2026-07-10" || days[1] != "2026-07-11" {
|
||
t.Fatalf("days=%v, want [2026-07-10 2026-07-11]", days)
|
||
}
|
||
if got := luckyGiftV3StatDays(endMS, startMS); got != nil {
|
||
t.Fatalf("inverted range must return nil, got %v", got)
|
||
}
|
||
}
|