mirror of
https://github.com/Dvorinka/Productier.git
synced 2026-06-04 20:43:02 +00:00
first commit
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestRequestMetricsObserveAndSnapshot(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
metrics := newRequestMetrics()
|
||||
metrics.observe(http.MethodGet, "/v1/health", http.StatusOK, 100*time.Millisecond)
|
||||
metrics.observe(http.MethodGet, "/v1/health", http.StatusOK, 200*time.Millisecond)
|
||||
metrics.observe(http.MethodPost, "/v1/tasks", http.StatusCreated, 40*time.Millisecond)
|
||||
|
||||
snapshot := metrics.snapshot()
|
||||
if snapshot.RequestsTotal != 3 {
|
||||
t.Fatalf("requestsTotal = %d, want 3", snapshot.RequestsTotal)
|
||||
}
|
||||
if snapshot.StatusClassTotals["2xx"] != 3 {
|
||||
t.Fatalf("2xx total = %d, want 3", snapshot.StatusClassTotals["2xx"])
|
||||
}
|
||||
if len(snapshot.Routes) != 2 {
|
||||
t.Fatalf("route bucket count = %d, want 2", len(snapshot.Routes))
|
||||
}
|
||||
if snapshot.UptimeSeconds < 0 {
|
||||
t.Fatalf("uptimeSeconds = %d, want >= 0", snapshot.UptimeSeconds)
|
||||
}
|
||||
|
||||
health := findRouteMetric(snapshot.Routes, http.MethodGet, "/v1/health", http.StatusOK)
|
||||
if health == nil {
|
||||
t.Fatal("missing route metric for GET /v1/health 200")
|
||||
}
|
||||
if health.Count != 2 {
|
||||
t.Fatalf("health count = %d, want 2", health.Count)
|
||||
}
|
||||
if health.AvgLatencyMs != 150 {
|
||||
t.Fatalf("health avgLatencyMs = %.2f, want 150", health.AvgLatencyMs)
|
||||
}
|
||||
if health.MaxLatencyMs != 200 {
|
||||
t.Fatalf("health maxLatencyMs = %.2f, want 200", health.MaxLatencyMs)
|
||||
}
|
||||
if _, err := time.Parse(time.RFC3339Nano, health.LastSeenAt); err != nil {
|
||||
t.Fatalf("health lastSeenAt parse error: %v", err)
|
||||
}
|
||||
if _, err := time.Parse(time.RFC3339Nano, snapshot.GeneratedAt); err != nil {
|
||||
t.Fatalf("snapshot generatedAt parse error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestMetricsMiddlewareSkipsMetricsEndpoint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
metrics := newRequestMetrics()
|
||||
router := gin.New()
|
||||
router.Use(requestMetricsMiddleware(metrics))
|
||||
router.GET("/v1/health", func(c *gin.Context) {
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
router.GET("/v1/metrics", func(c *gin.Context) {
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
router.GET("/v1/metrics/prometheus", func(c *gin.Context) {
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
|
||||
healthRequest := httptest.NewRequest(http.MethodGet, "/v1/health", nil)
|
||||
healthResponse := httptest.NewRecorder()
|
||||
router.ServeHTTP(healthResponse, healthRequest)
|
||||
if healthResponse.Code != http.StatusOK {
|
||||
t.Fatalf("GET /v1/health status = %d, want 200", healthResponse.Code)
|
||||
}
|
||||
|
||||
metricsRequest := httptest.NewRequest(http.MethodGet, "/v1/metrics", nil)
|
||||
metricsResponse := httptest.NewRecorder()
|
||||
router.ServeHTTP(metricsResponse, metricsRequest)
|
||||
if metricsResponse.Code != http.StatusOK {
|
||||
t.Fatalf("GET /v1/metrics status = %d, want 200", metricsResponse.Code)
|
||||
}
|
||||
|
||||
prometheusRequest := httptest.NewRequest(http.MethodGet, "/v1/metrics/prometheus", nil)
|
||||
prometheusResponse := httptest.NewRecorder()
|
||||
router.ServeHTTP(prometheusResponse, prometheusRequest)
|
||||
if prometheusResponse.Code != http.StatusOK {
|
||||
t.Fatalf("GET /v1/metrics/prometheus status = %d, want 200", prometheusResponse.Code)
|
||||
}
|
||||
|
||||
snapshot := metrics.snapshot()
|
||||
if snapshot.RequestsTotal != 1 {
|
||||
t.Fatalf("requestsTotal = %d, want 1", snapshot.RequestsTotal)
|
||||
}
|
||||
if findRouteMetric(snapshot.Routes, http.MethodGet, "/v1/metrics", http.StatusOK) != nil {
|
||||
t.Fatal("metrics endpoint request should be excluded from tracking")
|
||||
}
|
||||
if findRouteMetric(snapshot.Routes, http.MethodGet, "/v1/metrics/prometheus", http.StatusOK) != nil {
|
||||
t.Fatal("prometheus metrics endpoint request should be excluded from tracking")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotPrometheus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
metrics := newRequestMetrics()
|
||||
metrics.observe(http.MethodGet, "/v1/health", http.StatusOK, 50*time.Millisecond)
|
||||
metrics.observe(http.MethodGet, "/v1/tasks", http.StatusNotFound, 25*time.Millisecond)
|
||||
metrics.observe(http.MethodGet, `/v1/quoted"path`, http.StatusOK, 35*time.Millisecond)
|
||||
|
||||
output := metrics.snapshotPrometheus()
|
||||
expectedFragments := []string{
|
||||
"productier_http_uptime_seconds",
|
||||
`productier_http_requests_total{status_class="2xx"} 2`,
|
||||
`productier_http_requests_total{status_class="4xx"} 1`,
|
||||
`productier_http_requests_route_total{method="GET",path="/v1/health",status="200"} 1`,
|
||||
`productier_http_request_latency_avg_ms{method="GET",path="/v1/health",status="200"} 50.000`,
|
||||
`productier_http_request_latency_max_ms{method="GET",path="/v1/tasks",status="404"} 25.000`,
|
||||
`path="/v1/quoted\"path"`,
|
||||
}
|
||||
for _, fragment := range expectedFragments {
|
||||
if !strings.Contains(output, fragment) {
|
||||
t.Fatalf("expected prometheus output to contain %q\noutput:\n%s", fragment, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestItoa(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := map[int]string{
|
||||
0: "0",
|
||||
7: "7",
|
||||
42: "42",
|
||||
-10: "-10",
|
||||
2048: "2048",
|
||||
}
|
||||
|
||||
for input, want := range cases {
|
||||
if got := itoa(input); got != want {
|
||||
t.Fatalf("itoa(%d) = %q, want %q", input, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findRouteMetric(routes []routeMetricSnapshot, method, path string, status int) *routeMetricSnapshot {
|
||||
for _, route := range routes {
|
||||
if route.Method == method && route.Path == path && route.Status == status {
|
||||
result := route
|
||||
return &result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user