package monitoring import ( "context" "net/http" "net/http/httptest" "testing" "time" ) func TestPrometheusClient_Query(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/query" { t.Errorf("unexpected path: %s", r.URL.Path) } if r.URL.Query().Get("query") != "up" { t.Errorf("unexpected query param: %s", r.URL.Query().Get("query")) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{ "status": "success", "data": { "resultType": "vector", "result": [ { "metric": {"__name__": "up", "job": "prometheus", "instance": "localhost:9090"}, "value": [1234567890, "1"] }, { "metric": {"__name__": "up", "job": "node", "instance": "localhost:9100"}, "value": [1234567890, "0"] } ] } }`)) })) defer srv.Close() client := NewPrometheusClient(srv.URL) data, err := client.Query(context.Background(), "up", time.Time{}) if err != nil { t.Fatalf("unexpected error: %v", err) } if data.ResultType != "vector" { t.Errorf("expected resultType=vector, got %s", data.ResultType) } if len(data.Result) != 2 { t.Fatalf("expected 2 results, got %d", len(data.Result)) } if data.Result[0].Metric["job"] != "prometheus" { t.Errorf("expected job=prometheus, got %s", data.Result[0].Metric["job"]) } } func TestPrometheusClient_QueryError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{ "status": "error", "errorType": "bad_data", "error": "invalid expression" }`)) })) defer srv.Close() client := NewPrometheusClient(srv.URL) _, err := client.Query(context.Background(), "invalid{", time.Time{}) if err == nil { t.Fatal("expected error, got nil") } if !contains(err.Error(), "invalid expression") { t.Errorf("expected error to contain 'invalid expression', got: %s", err.Error()) } } func TestPrometheusClient_LabelValues(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/label/__name__/values" { t.Errorf("unexpected path: %s", r.URL.Path) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{ "status": "success", "data": ["up", "node_cpu_seconds_total", "prometheus_build_info"] }`)) })) defer srv.Close() client := NewPrometheusClient(srv.URL) values, err := client.LabelValues(context.Background(), "__name__") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(values) != 3 { t.Fatalf("expected 3 values, got %d", len(values)) } if values[0] != "up" { t.Errorf("expected first value=up, got %s", values[0]) } } func TestPrometheusClient_Metadata(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/metadata" { t.Errorf("unexpected path: %s", r.URL.Path) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{ "status": "success", "data": { "up": [{"type": "gauge", "help": "Whether the target is up.", "unit": ""}], "node_cpu_seconds_total": [{"type": "counter", "help": "CPU seconds spent.", "unit": "seconds"}] } }`)) })) defer srv.Close() client := NewPrometheusClient(srv.URL) metadata, err := client.Metadata(context.Background(), "") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(metadata) != 2 { t.Fatalf("expected 2 metrics, got %d", len(metadata)) } if metadata["up"][0].Type != "gauge" { t.Errorf("expected up type=gauge, got %s", metadata["up"][0].Type) } } func TestPrometheusClient_Targets(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/targets" { t.Errorf("unexpected path: %s", r.URL.Path) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{ "status": "success", "data": { "activeTargets": [ { "labels": {"instance": "localhost:9090", "job": "prometheus"}, "scrapePool": "prometheus", "scrapeUrl": "http://localhost:9090/metrics", "globalUrl": "http://localhost:9090/metrics", "lastError": "", "lastScrape": "2024-01-01T00:00:00Z", "lastScrapeDuration": 0.01, "health": "up", "scrapeInterval": "15s", "scrapeTimeout": "10s" } ], "droppedTargets": [] } }`)) })) defer srv.Close() client := NewPrometheusClient(srv.URL) data, err := client.Targets(context.Background()) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(data.ActiveTargets) != 1 { t.Fatalf("expected 1 active target, got %d", len(data.ActiveTargets)) } if data.ActiveTargets[0].Health != "up" { t.Errorf("expected health=up, got %s", data.ActiveTargets[0].Health) } } func TestPrometheusClient_HTTPError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte("internal error")) })) defer srv.Close() client := NewPrometheusClient(srv.URL) _, err := client.Query(context.Background(), "up", time.Time{}) if err == nil { t.Fatal("expected error, got nil") } if !contains(err.Error(), "500") { t.Errorf("expected error to contain status code, got: %s", err.Error()) } } func contains(s, substr string) bool { return len(s) >= len(substr) && searchString(s, substr) } func searchString(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }