package server import ( "ai-router/internal/protocol" "encoding/json" "net/url" "reflect" "testing" ) func TestStatsBrowserTimezone(t *testing.T) { for _, tc := range []struct { name, zone string stamps []string want map[string]int }{ {"Shanghai midnight", "Asia/Shanghai", []string{"2026-10-20T15:59:59Z", "2026-10-20T16:00:00Z"}, map[string]int{"2026-10-20": 1, "2026-10-21": 1}}, {"UTC default", "", []string{"2026-10-20T15:59:59Z", "2026-10-20T16:00:00Z"}, map[string]int{"2026-10-20": 2}}, {"New York DST spring", "America/New_York", []string{"2025-03-09T04:59:59Z", "2025-03-09T05:00:00Z", "2025-03-10T03:59:59Z", "2025-03-10T04:00:00Z"}, map[string]int{"2025-03-08": 1, "2025-03-09": 2, "2025-03-10": 1}}, {"New York DST fall", "America/New_York", []string{"2025-11-02T03:59:59Z", "2025-11-02T04:00:00Z", "2025-11-03T04:59:59Z", "2025-11-03T05:00:00Z"}, map[string]int{"2025-11-01": 1, "2025-11-02": 2, "2025-11-03": 1}}, } { t.Run(tc.name, func(t *testing.T) { s := testServer(t) cookie := loginCookie(t, s) for _, stamp := range tc.stamps { s.record(requestLog{CreatedAt: stamp, KeyID: "one", Status: 200, Usage: protocol.Usage{Known: true, Input: 10}}) } s.record(requestLog{CreatedAt: tc.stamps[0], KeyID: "other", Status: 200}) if err := s.flushLogs(); err != nil { t.Fatal(err) } w := call(s, "GET", "/admin/api/stats?key_id=one&timezone="+url.QueryEscape(tc.zone), "", cookie) if w.Code != 200 { t.Fatal(w.Code, w.Body.String()) } var result struct { Timezone string `json:"timezone"` Summary summary `json:"summary"` Daily []struct { Day string `json:"day"` Requests int `json:"requests"` } `json:"daily"` } if err := json.Unmarshal(w.Body.Bytes(), &result); err != nil { t.Fatal(err) } got := map[string]int{} for _, d := range result.Daily { got[d.Day] = d.Requests } if !reflect.DeepEqual(got, tc.want) { t.Fatalf("got %v want %v", got, tc.want) } if result.Summary.Requests != int64(len(tc.stamps)) || result.Summary.Input == nil || *result.Summary.Input != int64(10*len(tc.stamps)) { t.Fatal(result.Summary) } var stored string if err := s.db.QueryRow("SELECT created_at FROM logs ORDER BY id LIMIT 1").Scan(&stored); err != nil || stored != tc.stamps[0] { t.Fatal("UTC storage changed", stored, err) } }) } } func TestStatsRejectsInvalidTimezone(t *testing.T) { s := testServer(t) c := loginCookie(t, s) for _, zone := range []string{"Local", "not/a-zone", "../../etc/passwd"} { w := call(s, "GET", "/admin/api/stats?timezone="+url.QueryEscape(zone), "", c) if w.Code != 400 { t.Fatal(zone, w.Code) } } }