package store_test import ( "context" "testing" "time" "github.com/wangjia/pangolin/server/internal/usage" ) // The timezone-fix proof: usage stored at UTC 18:00 yesterday must land on a // DIFFERENT local calendar day under UTC (offset 0) vs UTC+8 (offset 480) — // exactly the "今天却显示昨天数据" bug. UsageCurve re-buckets UTC hourly rows by // the client's local day so "today" matches the user's calendar. func TestSQLite_UsageCurve_LocalDayBucketing(t *testing.T) { ctx := context.Background() db := openSQLite(t) // Yesterday 18:00 UTC = today 02:00 in UTC+8 → straddles the day boundary. now := time.Now().UTC() y := now.AddDate(0, 0, -1) utc18 := time.Date(y.Year(), y.Month(), y.Day(), 18, 0, 0, 0, time.UTC) hourEpoch := utc18.Unix() / 3600 if _, err := db.Exec( `INSERT INTO usage_hourly (user_id, hour, bytes_up, bytes_down, minutes_used) VALUES (1,?,?,?,?)`, hourEpoch, 1000, 2000, 5); err != nil { t.Fatalf("seed: %v", err) } cn := time.FixedZone("UTC+8", 8*3600) utcDate := utc18.Format("2006-01-02") // yesterday (UTC) cnDate := utc18.In(cn).Format("2006-01-02") // today (UTC+8) if utcDate == cnDate { t.Fatalf("test setup expected different dates, got %s == %s", utcDate, cnDate) } svc := usage.NewService(usage.NewStore(db), nil, nil, time.Hour) find := func(days, offset int, date string) (usage.UsagePoint, bool) { pts, e := svc.UsageCurve(ctx, 1, days, offset, "") if e != nil { t.Fatalf("UsageCurve(off=%d): %v", offset, e) } for _, p := range pts { if p.Date == date { return p, true } } return usage.UsagePoint{}, false } // Offset 0 (UTC): the data is on yesterday's UTC date. if p, ok := find(3, 0, utcDate); !ok || p.BytesDown != 2000 || p.BytesUp != 1000 || p.MinutesUsed != 5 { t.Fatalf("UTC bucket: want data on %s, got ok=%v %+v", utcDate, ok, p) } // Offset +480 (UTC+8): the SAME data is on today's local date instead. if p, ok := find(3, 480, cnDate); !ok || p.BytesDown != 2000 { t.Fatalf("UTC+8 bucket: want data on %s, got ok=%v %+v", cnDate, ok, p) } // And under +480 it must NOT appear on the UTC date. if p, ok := find(3, 480, utcDate); ok && p.BytesDown != 0 { t.Fatalf("UTC+8: data leaked onto UTC date %s: %+v", utcDate, p) } }