zhx bf6d7d4f1e 幸运礼物实验硬约束:两组 v2 高水位加权三元组必须一致
滞回标志按 app+pool 单行存储且由每个用户钉住的组内配置推进;
两组三元组不同(尤其 control 关/treatment 开的灰度形态)会互踩:
control 逐抽清掉 treatment 的标志,阈值不同的组按对方状态加权付奖。
加权特性的灰度必须走常规发布,不能走实验。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 02:07:24 +08:00

111 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}