Files
sub2api-cn-relay-manager/internal/metrics/metrics_test.go
phamnazage-jpg d688722dd2 feat(metrics): H-04 Prometheus 指标暴露
- 创建 internal/metrics 包集成 Prometheus 客户端
- 添加 HTTP 请求指标(总量、延迟直方图)
- 添加业务指标(active_hosts、active_providers)
- 添加路由指标(decisions、failovers)
- 添加数据库指标(connections、operations)
- 添加日志指标(flush_errors、dropped_events)
- 添加 HTTP Middleware 自动收集请求指标
- 添加 StartServer 方法启动独立 metrics 服务
2026-06-02 06:53:24 +08:00

228 lines
5.4 KiB
Go

package metrics
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
func TestHTTPRequestsTotal(t *testing.T) {
RecordHTTPRequest("GET", "/test", 200, 100*time.Millisecond)
// Verify the counter was incremented
// Note: We can't easily read the counter value directly, but we can verify
// the handler returns the metric
req := httptest.NewRequest("GET", "/metrics", nil)
rr := httptest.NewRecorder()
Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "http_requests_total") {
t.Error("Expected metrics endpoint to contain http_requests_total")
}
}
func TestRecordRouteDecision(t *testing.T) {
RecordRouteDecision("test-group", "success")
RecordRouteDecision("test-group", "success")
RecordRouteDecision("test-group", "failed")
req := httptest.NewRequest("GET", "/metrics", nil)
rr := httptest.NewRecorder()
Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "route_decisions_total") {
t.Error("Expected metrics endpoint to contain route_decisions_total")
}
}
func TestRecordRouteFailover(t *testing.T) {
RecordRouteFailover()
req := httptest.NewRequest("GET", "/metrics", nil)
rr := httptest.NewRecorder()
Handler().ServeHTTP(rr, req)
body := rr.Body.String()
if !strings.Contains(body, "route_failovers_total") {
t.Error("Expected metrics endpoint to contain route_failovers_total")
}
}
func TestSetActiveHosts(t *testing.T) {
SetActiveHosts(10)
req := httptest.NewRequest("GET", "/metrics", nil)
rr := httptest.NewRecorder()
Handler().ServeHTTP(rr, req)
body := rr.Body.String()
if !strings.Contains(body, "active_hosts") {
t.Error("Expected metrics endpoint to contain active_hosts")
}
}
func TestSetActiveProviders(t *testing.T) {
SetActiveProviders(5)
req := httptest.NewRequest("GET", "/metrics", nil)
rr := httptest.NewRecorder()
Handler().ServeHTTP(rr, req)
body := rr.Body.String()
if !strings.Contains(body, "active_providers") {
t.Error("Expected metrics endpoint to contain active_providers")
}
}
func TestRecordDBOperation(t *testing.T) {
RecordDBOperation("insert", "hosts")
RecordDBOperation("select", "hosts")
req := httptest.NewRequest("GET", "/metrics", nil)
rr := httptest.NewRecorder()
Handler().ServeHTTP(rr, req)
body := rr.Body.String()
if !strings.Contains(body, "db_operations_total") {
t.Error("Expected metrics endpoint to contain db_operations_total")
}
}
func TestRecordLogFlushError(t *testing.T) {
RecordLogFlushError()
RecordLogFlushError()
req := httptest.NewRequest("GET", "/metrics", nil)
rr := httptest.NewRecorder()
Handler().ServeHTTP(rr, req)
body := rr.Body.String()
if !strings.Contains(body, "log_flush_errors_total") {
t.Error("Expected metrics endpoint to contain log_flush_errors_total")
}
}
func TestRecordLogDroppedEvent(t *testing.T) {
RecordLogDroppedEvent()
req := httptest.NewRequest("GET", "/metrics", nil)
rr := httptest.NewRecorder()
Handler().ServeHTTP(rr, req)
body := rr.Body.String()
if !strings.Contains(body, "log_dropped_events_total") {
t.Error("Expected metrics endpoint to contain log_dropped_events_total")
}
}
func TestMiddleware(t *testing.T) {
handler := Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
w.Write([]byte("OK"))
}))
req := httptest.NewRequest("POST", "/api/test", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusCreated {
t.Errorf("Expected status 201, got %d", rr.Code)
}
if rr.Body.String() != "OK" {
t.Errorf("Expected body 'OK', got '%s'", rr.Body.String())
}
}
func TestResponseWriter(t *testing.T) {
base := httptest.NewRecorder()
rw := &responseWriter{ResponseWriter: base, statusCode: 200}
rw.WriteHeader(http.StatusTeapot)
if rw.statusCode != http.StatusTeapot {
t.Errorf("Expected status code %d, got %d", http.StatusTeapot, rw.statusCode)
}
}
func TestStartServer(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Start metrics server on a random port
server := StartServer(ctx, "127.0.0.1:0")
// Give server time to start
time.Sleep(100 * time.Millisecond)
// Verify server is running by making a request
addr := server.Addr
if addr == "" {
t.Fatal("Server address not available")
}
// Server should shut down gracefully when context is cancelled
cancel()
// Give server time to shut down
time.Sleep(100 * time.Millisecond)
}
func TestHandlerContent(t *testing.T) {
// Set some metrics values
SetActiveHosts(42)
SetActiveProviders(7)
req := httptest.NewRequest("GET", "/metrics", nil)
rr := httptest.NewRecorder()
Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", rr.Code)
}
contentType := rr.Header().Get("Content-Type")
if !strings.Contains(contentType, "text/plain") && !strings.Contains(contentType, "application/openmetrics") {
t.Errorf("Expected text/plain or openmetrics content type, got %s", contentType)
}
body := rr.Body.String()
// Check for expected metrics
expectedMetrics := []string{
"# HELP",
"# TYPE",
"active_hosts",
"active_providers",
}
for _, metric := range expectedMetrics {
if !strings.Contains(body, metric) {
t.Errorf("Expected metrics to contain '%s'", metric)
}
}
}