package main import ( "net/http/httptest" "strings" "testing" ) func TestPromStoreIngestBodyBuckets(t *testing.T) { ps := NewPromStore() // 512 bytes: > 256, ≤ 1024 → bucket[0] stays 0, buckets[1..N] get 1 ps.Ingest(LogRecord{Website: "example.com", Method: "GET", Status: "200", BodyBytesSent: 512}) ps.mu.Lock() be := ps.body["example.com"] ps.mu.Unlock() if be == nil { t.Fatal("expected body entry, got nil") } if be.buckets[0] != 0 { // le=256: 512 > 256 t.Errorf("le=256 bucket = %d, want 0", be.buckets[0]) } if be.buckets[1] != 1 { // le=1024: 512 ≤ 1024 t.Errorf("le=1024 bucket = %d, want 1", be.buckets[1]) } for i := 2; i <= promNumBodyBounds; i++ { if be.buckets[i] != 1 { t.Errorf("bucket[%d] = %d, want 1", i, be.buckets[i]) } } if be.sum != 512 { t.Errorf("sum = %d, want 512", be.sum) } } func TestPromStoreIngestTimeBuckets(t *testing.T) { ps := NewPromStore() // 0.075s: > 0.05, ≤ 0.1 ps.Ingest(LogRecord{Website: "example.com", Method: "GET", Status: "200", RequestTime: 0.075}) ps.mu.Lock() te := ps.reqTime["example.com"] ps.mu.Unlock() if te == nil { t.Fatal("expected time entry, got nil") } // le=0.05 (index 3): 0.075 > 0.05 → 0 if te.buckets[3] != 0 { t.Errorf("le=0.05 bucket = %d, want 0", te.buckets[3]) } // le=0.1 (index 4): 0.075 ≤ 0.1 → 1 if te.buckets[4] != 1 { t.Errorf("le=0.1 bucket = %d, want 1", te.buckets[4]) } // +Inf (last): always 1 if te.buckets[promNumTimeBounds] != 1 { t.Errorf("+Inf bucket = %d, want 1", te.buckets[promNumTimeBounds]) } } func TestPromStoreCounter(t *testing.T) { ps := NewPromStore() ps.Ingest(LogRecord{Website: "a.com", Method: "GET", Status: "200"}) ps.Ingest(LogRecord{Website: "a.com", Method: "GET", Status: "200"}) ps.Ingest(LogRecord{Website: "a.com", Method: "POST", Status: "201"}) ps.mu.Lock() c1 := ps.counters[promCounterKey{"a.com", "GET", "200"}] c2 := ps.counters[promCounterKey{"a.com", "POST", "201"}] ps.mu.Unlock() if c1 != 2 { t.Errorf("GET/200 count = %d, want 2", c1) } if c2 != 1 { t.Errorf("POST/201 count = %d, want 1", c2) } } func TestPromStoreServeHTTP(t *testing.T) { ps := NewPromStore() ps.Ingest(LogRecord{ Website: "example.com", Method: "GET", Status: "200", BodyBytesSent: 100, RequestTime: 0.042, }) req := httptest.NewRequest("GET", "/metrics", nil) rec := httptest.NewRecorder() ps.ServeHTTP(rec, req) body := rec.Body.String() checks := []string{ "# TYPE nginx_http_requests_total counter", `nginx_http_requests_total{host="example.com",method="GET",status="200"} 1`, "# TYPE nginx_http_response_body_bytes histogram", `nginx_http_response_body_bytes_bucket{host="example.com",le="256"} 1`, // 100 ≤ 256 `nginx_http_response_body_bytes_count{host="example.com"} 1`, `nginx_http_response_body_bytes_sum{host="example.com"} 100`, "# TYPE nginx_http_request_duration_seconds histogram", `nginx_http_request_duration_seconds_bucket{host="example.com",le="0.05"} 1`, // 0.042 ≤ 0.05 `nginx_http_request_duration_seconds_count{host="example.com"} 1`, } for _, want := range checks { if !strings.Contains(body, want) { t.Errorf("missing %q in output:\n%s", want, body) } } } func TestPromStoreCounterCap(t *testing.T) { ps := NewPromStore() // Fill to cap with distinct {host,method,status} combos for i := 0; i < promCounterCap+10; i++ { host := strings.Repeat("x", i%10+1) + ".com" status := "200" if i%3 == 0 { status = "404" } ps.Ingest(LogRecord{Website: host, Method: "GET", Status: status}) } ps.mu.Lock() n := len(ps.counters) ps.mu.Unlock() if n > promCounterCap { t.Errorf("counter map size %d exceeds cap %d", n, promCounterCap) } }