bindbox-game/internal/api/admin/dashboard_admin_test.go
Zuncle c7a6e1e017 fix(dashboard): 修正销售抽奖趋势自定义日期统计
修复 sales_draw_trend 自定义日期按 UTC 解析导致的统计偏差,并统一使用半开区间处理日维度边界,补充上海时区与单日范围回归测试。
2026-04-04 00:06:17 +08:00

200 lines
5.3 KiB
Go
Executable File

package admin
import (
"encoding/json"
"strings"
"testing"
"time"
)
func useShanghaiLocal(t *testing.T) *time.Location {
t.Helper()
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
t.Fatalf("load Asia/Shanghai: %v", err)
}
oldLocal := time.Local
time.Local = loc
t.Cleanup(func() {
time.Local = oldLocal
})
return loc
}
func TestPercentChange(t *testing.T) {
if s := percentChange(0, 10); s != "+0%" {
t.Fatalf("prev0")
}
if s := percentChange(10, 15); s == "" {
t.Fatalf("empty")
}
}
func TestPreviousWindow(t *testing.T) {
s := time.Date(2025, 1, 1, 0, 0, 0, 0, time.Local)
e := s.Add(7 * 24 * time.Hour).Add(-time.Second)
ps, pe := previousWindow(s, e)
if !pe.Before(s) {
t.Fatalf("pe not before start")
}
if ps.After(pe) {
t.Fatalf("ps after pe")
}
}
func TestDaysBetween(t *testing.T) {
s := time.Date(2025, 1, 1, 0, 0, 0, 0, time.Local)
e := time.Date(2025, 1, 03, 23, 59, 59, 0, time.Local)
ds := daysBetween(s, e)
if len(ds) != 3 {
t.Fatalf("expect3")
}
}
func TestParseSalesTrendRange_PrefersStartEnd(t *testing.T) {
loc := useShanghaiLocal(t)
now := time.Date(2026, 3, 3, 12, 0, 0, 0, time.UTC)
s, e := parseSalesTrendRange("week", "2026-01-01", "2026-01-10", now, nil)
expectedStart := time.Date(2026, 1, 1, 0, 0, 0, 0, loc)
expectedEnd := time.Date(2026, 1, 11, 0, 0, 0, 0, loc)
if !s.Equal(expectedStart) {
t.Fatalf("unexpected start: %v", s)
}
if !e.Equal(expectedEnd) {
t.Fatalf("unexpected end: %v", e)
}
}
func TestParseSalesTrendRange_AliasAndAllFallback(t *testing.T) {
now := time.Date(2026, 3, 3, 12, 0, 0, 0, time.UTC)
sWeek, eWeek := parseSalesTrendRange("week", "", "", now, nil)
if !eWeek.Equal(now) {
t.Fatalf("week end should be now")
}
if sWeek.Sub(eWeek) != -7*24*time.Hour {
t.Fatalf("week should be 7 days")
}
sMonth, eMonth := parseSalesTrendRange("month", "", "", now, nil)
if !eMonth.Equal(now) {
t.Fatalf("month end should be now")
}
if sMonth.Sub(eMonth) != -30*24*time.Hour {
t.Fatalf("month should be 30 days")
}
sAllFallback, eAllFallback := parseSalesTrendRange("all", "", "", now, nil)
if !eAllFallback.Equal(now) {
t.Fatalf("all fallback end should be now")
}
if sAllFallback.Sub(eAllFallback) != -30*24*time.Hour {
t.Fatalf("all fallback should be 30 days")
}
allStart := time.Date(2025, 1, 15, 10, 0, 0, 0, time.UTC)
sAll, eAll := parseSalesTrendRange("all", "", "", now, &allStart)
if !sAll.Equal(time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC)) {
t.Fatalf("all start should align to date start, got: %v", sAll)
}
if !eAll.Equal(now) {
t.Fatalf("all end should be now")
}
}
func TestParseSalesTrendRange_CustomSpanCap(t *testing.T) {
loc := useShanghaiLocal(t)
now := time.Date(2026, 3, 3, 12, 0, 0, 0, time.UTC)
s, e := parseSalesTrendRange("custom", "2025-01-01", "2026-12-31", now, nil)
expectedStart := time.Date(2025, 1, 1, 0, 0, 0, 0, loc)
expectedEnd := expectedStart.Add(366 * 24 * time.Hour)
if !s.Equal(expectedStart) {
t.Fatalf("unexpected custom start: %v", s)
}
if !e.Equal(expectedEnd) {
t.Fatalf("unexpected custom end: %v", e)
}
}
func TestParseSalesTrendRange_CustomSingleDayLocal(t *testing.T) {
loc := useShanghaiLocal(t)
now := time.Date(2026, 3, 3, 12, 0, 0, 0, time.UTC)
s, e := parseSalesTrendRange("custom", "2026-03-01", "2026-03-01", now, nil)
expectedStart := time.Date(2026, 3, 1, 0, 0, 0, 0, loc)
expectedEnd := time.Date(2026, 3, 2, 0, 0, 0, 0, loc)
if !s.Equal(expectedStart) {
t.Fatalf("unexpected custom single-day start: %v", s)
}
if !e.Equal(expectedEnd) {
t.Fatalf("unexpected custom single-day end: %v", e)
}
}
func TestShouldUseMonthlyGranularityForAll(t *testing.T) {
start := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
endLong := start.Add(181 * 24 * time.Hour)
endShort := start.Add(180 * 24 * time.Hour)
if !shouldUseMonthlyGranularityForAll("all", "day", start, endLong) {
t.Fatalf("expected monthly for all with >180 days")
}
if shouldUseMonthlyGranularityForAll("all", "day", start, endShort) {
t.Fatalf("should not switch for exactly 180 days")
}
if shouldUseMonthlyGranularityForAll("week", "day", start, endLong) {
t.Fatalf("non-all rangeType should not switch")
}
if shouldUseMonthlyGranularityForAll("all", "month", start, endLong) {
t.Fatalf("non-day granularity should not switch")
}
}
func TestBuildBuckets_DayUsesHalfOpenInterval(t *testing.T) {
loc := useShanghaiLocal(t)
start := time.Date(2026, 3, 1, 0, 0, 0, 0, loc)
endExclusive := time.Date(2026, 3, 2, 0, 0, 0, 0, loc)
buckets := buildBuckets(start, endExclusive, "day")
if len(buckets) != 1 {
t.Fatalf("expected 1 bucket, got %d", len(buckets))
}
b := buckets[0]
if !b.Start.Equal(start) {
t.Fatalf("unexpected bucket start: %v", b.Start)
}
if !b.End.Equal(time.Date(2026, 3, 1, 23, 59, 59, 0, loc)) {
t.Fatalf("unexpected bucket inclusive end: %v", b.End)
}
if !b.EndExclusive.Equal(endExclusive) {
t.Fatalf("unexpected bucket exclusive end: %v", b.EndExclusive)
}
}
func TestTrendPointJSONIncludesPaidAmount(t *testing.T) {
point := trendPoint{
Date: "2026-03-01",
Value: 10,
Gmv: 20,
PaidAmount: 15,
Orders: 3,
NewUsers: 2,
}
payload, err := json.Marshal(point)
if err != nil {
t.Fatalf("marshal trendPoint: %v", err)
}
data := string(payload)
if !strings.Contains(data, "\"paidAmount\":15") {
t.Fatalf("expected paidAmount in json, got: %s", data)
}
}