package services import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "lokistack/models" ) // TestLokiMetricsParsesMatrixSeries 用假 Loki 验证指标代理: // [$step] 占位符替换 + matrix 多序列解析(按 app/namespace 分组)。 func TestLokiMetricsParsesMatrixSeries(t *testing.T) { var gotQuery, gotStep string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotQuery = r.URL.Query().Get("query") gotStep = r.URL.Query().Get("step") w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{ "status": "success", "data": { "resultType": "matrix", "result": [ { "metric": {"app": "web", "namespace": "prod"}, "values": [[1.79e+09, "10"], [1.79e+09, "20"]] }, { "metric": {"app": "api", "namespace": "prod"}, "values": [[1.79e+09, "5"]] }, { "metric": {"app": "empty", "namespace": "prod"}, "values": [] } ] } }`)) })) defer server.Close() src := &models.LogSource{TargetType: "url", URL: server.URL, AuthType: "none"} query := `sum by (app, namespace) (count_over_time({namespace=~".+"}[$step]))` end := time.Now().UnixNano() start := end - int64(time.Hour) series, err := LokiMetrics(context.Background(), src, query, start, end) if err != nil { t.Fatalf("LokiMetrics: %v", err) } // 占位符被替换为 1h 跨度对应的步长,且 query_range 带上了同样的 step if gotQuery == query || !strings.Contains(gotQuery, `count_over_time({namespace=~".+"}[`) { t.Fatalf("[$step] not replaced, got %q", gotQuery) } if gotStep == "" || !strings.Contains(gotQuery, "["+gotStep+"]") { t.Fatalf("step mismatch: query=%q step=%q", gotQuery, gotStep) } // 空序列被丢弃,两条有效序列带标签返回 if len(series) != 2 { t.Fatalf("want 2 series, got %d", len(series)) } if series[0].Labels["app"] != "web" || series[0].Labels["namespace"] != "prod" { t.Fatalf("labels not parsed: %+v", series[0].Labels) } if len(series[0].Points) != 2 || series[0].Points[0].Value != 10 || series[0].Points[1].Value != 20 { t.Fatalf("points not parsed: %+v", series[0].Points) } if series[0].Points[0].Timestamp == "" { t.Fatal("timestamp not formatted") } // 响应可 JSON 序列化(controller 直接 utils.Success 包装) if _, err := json.Marshal(series); err != nil { t.Fatalf("marshal: %v", err) } }