package service import ( "testing" "time" "oci-portal/internal/oci" ) func TestAlignDown(t *testing.T) { tests := []struct { name string in time.Time granularity string want time.Time }{ { name: "daily 截到当天零点", in: time.Date(2026, 7, 3, 15, 4, 5, 0, time.UTC), granularity: "DAILY", want: time.Date(2026, 7, 3, 0, 0, 0, 0, time.UTC), }, { name: "daily 已在边界不变", in: time.Date(2026, 7, 3, 0, 0, 0, 0, time.UTC), granularity: "DAILY", want: time.Date(2026, 7, 3, 0, 0, 0, 0, time.UTC), }, { name: "monthly 截到当月一号", in: time.Date(2026, 7, 15, 8, 0, 0, 0, time.UTC), granularity: "MONTHLY", want: time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := alignDown(tt.in, tt.granularity); !got.Equal(tt.want) { t.Errorf("alignDown() = %v, want %v", got, tt.want) } }) } } func TestAlignUp(t *testing.T) { tests := []struct { name string in time.Time granularity string want time.Time }{ { name: "daily 进到明日零点覆盖当天", in: time.Date(2026, 7, 3, 15, 4, 5, 0, time.UTC), granularity: "DAILY", want: time.Date(2026, 7, 4, 0, 0, 0, 0, time.UTC), }, { name: "daily 已在边界不进位", in: time.Date(2026, 7, 3, 0, 0, 0, 0, time.UTC), granularity: "DAILY", want: time.Date(2026, 7, 3, 0, 0, 0, 0, time.UTC), }, { name: "monthly 进到下月一号覆盖当月", in: time.Date(2026, 7, 15, 8, 0, 0, 0, time.UTC), granularity: "MONTHLY", want: time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC), }, { name: "monthly 十二月进位跨年", in: time.Date(2026, 12, 20, 0, 0, 0, 0, time.UTC), granularity: "MONTHLY", want: time.Date(2027, 1, 1, 0, 0, 0, 0, time.UTC), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := alignUp(tt.in, tt.granularity); !got.Equal(tt.want) { t.Errorf("alignUp() = %v, want %v", got, tt.want) } }) } } func TestApplyCostDefaults(t *testing.T) { var q oci.CostQuery applyCostDefaults(&q) if q.Granularity != "DAILY" || q.QueryType != "COST" || q.GroupBy != "service" { t.Errorf("defaults = %s/%s/%s, want DAILY/COST/service", q.Granularity, q.QueryType, q.GroupBy) } if q.StartTime.IsZero() || q.EndTime.IsZero() { t.Fatal("time window not defaulted") } window := q.EndTime.Sub(q.StartTime) if window < 30*24*time.Hour || window > 32*24*time.Hour { t.Errorf("default window = %v, want ~30d", window) } if !q.EndTime.After(time.Now().UTC()) { t.Errorf("endTime %v should be aligned up past now (open interval)", q.EndTime) } } func TestFilterCostWindow(t *testing.T) { hour := func(h int) *time.Time { ts := time.Date(2026, 7, 20, h, 0, 0, 0, time.UTC) return &ts } start := time.Date(2026, 7, 20, 16, 0, 0, 0, time.UTC) end := time.Date(2026, 7, 21, 0, 0, 0, 0, time.UTC) tests := []struct { name string items []oci.CostItem want int }{ {name: "空输入", items: []oci.CostItem{}, want: 0}, {name: "窗口前的整日下扩数据被丢弃", items: []oci.CostItem{{TimeStart: hour(0)}, {TimeStart: hour(15)}}, want: 0}, {name: "起点边界含", items: []oci.CostItem{{TimeStart: hour(16)}}, want: 1}, {name: "终点边界不含", items: []oci.CostItem{{TimeStart: &end}}, want: 0}, {name: "TimeStart 缺失丢弃", items: []oci.CostItem{{TimeStart: nil}}, want: 0}, {name: "窗口内外混合只留窗口内", items: []oci.CostItem{{TimeStart: hour(2)}, {TimeStart: hour(16)}, {TimeStart: hour(23)}, {TimeStart: &end}}, want: 2}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := filterCostWindow(tt.items, start, end) if len(got) != tt.want { t.Errorf("filterCostWindow() kept %d rows, want %d", len(got), tt.want) } for _, it := range got { if it.TimeStart.Before(start) || !it.TimeStart.Before(end) { t.Errorf("row %v outside window [%v, %v)", it.TimeStart, start, end) } } }) } } func TestValidateEmails(t *testing.T) { tests := []struct { name string emails []string wantErr bool }{ {name: "空列表合法表示关闭 test mode", emails: nil, wantErr: false}, {name: "正常邮箱", emails: []string{"a@b.com", "c@d.org"}, wantErr: false}, {name: "缺少@", emails: []string{"not-an-email"}, wantErr: true}, {name: "含空格", emails: []string{"a b@c.com"}, wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := validateEmails(tt.emails); (err != nil) != tt.wantErr { t.Errorf("validateEmails(%v) error = %v, wantErr %v", tt.emails, err, tt.wantErr) } }) } }