200 lines
5.3 KiB
Go
Executable File
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)
|
|
}
|
|
}
|