mirror of
https://github.com/Dvorinka/Productier.git
synced 2026-06-03 20:13:01 +00:00
158 lines
5.1 KiB
Go
158 lines
5.1 KiB
Go
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
|
|
}
|