@@ -18,12 +18,15 @@ func costItem(day string, amount float32, currency string) oci.CostItem {
|
||||
}
|
||||
|
||||
func TestRunCostTaskSkipsFreeAndUpserts(t *testing.T) {
|
||||
// 日期取相对当前的近两天:任务查近 7 天,窗口外的行会被 Costs 过滤
|
||||
day1 := time.Now().UTC().AddDate(0, 0, -2).Format("2006-01-02")
|
||||
day2 := time.Now().UTC().AddDate(0, 0, -1).Format("2006-01-02")
|
||||
client := &fakeClient{
|
||||
tenancy: oci.TenancyInfo{Name: "acme", HomeRegionKey: "FRA"},
|
||||
costItems: []oci.CostItem{
|
||||
costItem("2026-07-01", 5.2, "USD"),
|
||||
costItem("2026-07-01", 1.4, "USD"),
|
||||
costItem("2026-07-02", 6.3, "USD"),
|
||||
costItem(day1, 5.2, "USD"),
|
||||
costItem(day1, 1.4, "USD"),
|
||||
costItem(day2, 6.3, "USD"),
|
||||
},
|
||||
}
|
||||
tasks, configs, db := newTaskEnv(t, client)
|
||||
@@ -55,13 +58,13 @@ func TestRunCostTaskSkipsFreeAndUpserts(t *testing.T) {
|
||||
t.Errorf("log = %+v, want synced 1 skipped 1", entry)
|
||||
}
|
||||
|
||||
assertCostSnapshots(t, db, map[string]float64{"2026-07-01": 6.6, "2026-07-02": 6.3})
|
||||
assertCostSnapshots(t, db, map[string]float64{day1: 6.6, day2: 6.3})
|
||||
|
||||
// 再次执行为覆盖更新,不产生重复行
|
||||
if _, err := tasks.RunTaskNow(ctx, task.ID); err != nil {
|
||||
t.Fatalf("RunTaskNow again: %v", err)
|
||||
}
|
||||
assertCostSnapshots(t, db, map[string]float64{"2026-07-01": 6.6, "2026-07-02": 6.3})
|
||||
assertCostSnapshots(t, db, map[string]float64{day1: 6.6, day2: 6.3})
|
||||
}
|
||||
|
||||
// assertCostSnapshots 断言快照表恰好为 want 中的日期与金额(配置 #1)。
|
||||
|
||||
@@ -45,7 +45,28 @@ func (s *OciConfigService) Costs(ctx context.Context, id uint, q oci.CostQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.client.SummarizeCosts(ctx, cred, q)
|
||||
items, err := s.client.SummarizeCosts(ctx, cred, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filterCostWindow(items, q.StartTime, q.EndTime), nil
|
||||
}
|
||||
|
||||
// filterCostWindow 只保留落在请求窗口 [start, end) 内的行:Usage API 的
|
||||
// HOURLY 粒度会把起点下扩到 UTC 日零点,返回窗口之外的整日数据。
|
||||
func filterCostWindow(items []oci.CostItem, start, end time.Time) []oci.CostItem {
|
||||
out := items[:0]
|
||||
for _, it := range items {
|
||||
if it.TimeStart == nil {
|
||||
continue
|
||||
}
|
||||
ts := it.TimeStart.UTC()
|
||||
if ts.Before(start) || !ts.Before(end) {
|
||||
continue
|
||||
}
|
||||
out = append(out, it)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// applyCostDefaults 填充缺省值并把时间对齐到粒度边界(Usage API 要求)。
|
||||
|
||||
@@ -101,6 +101,40 @@ func TestApplyCostDefaults(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user