Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions internal/datagen/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package datagen

import "math/rand"

// HTTP data pools for log generation.
var (
// Methods is a pool of standard HTTP methods.
Methods = NewPool("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS")

// Protocols is a pool of HTTP protocol versions.
Protocols = NewPool("HTTP/1.0", "HTTP/1.1", "HTTP/2.0")

// Status code pools grouped by class.
Status2xx = NewPool(200, 201, 202, 204)
Status3xx = NewPool(301, 302, 304, 307, 308)
Status4xx = NewPool(400, 401, 403, 404, 405, 408, 429)
Status5xx = NewPool(500, 502, 503, 504)

// APIPaths is a pool of common API and web paths.
APIPaths = NewPool(
"/api/v1/users", "/api/v1/orders", "/api/v1/products",
"/api/v1/auth/login", "/api/v1/auth/logout", "/api/v1/auth/refresh",
"/api/v2/data", "/api/v2/search", "/api/v2/metrics",
"/health", "/healthz", "/ready", "/status",
"/", "/about", "/contact", "/dashboard", "/settings",
"/login", "/logout", "/register", "/profile",
"/search", "/download", "/upload",
)

// RefererDomains is a pool of domains used in HTTP referer headers.
RefererDomains = NewPool(
"google.com", "bing.com", "github.com", "stackoverflow.com",
"reddit.com", "linkedin.com", "example.com", "internal.corp",
)
)

// RandomStatusCode returns a weighted random HTTP status code.
// Distribution: ~70% 2xx, ~5% 3xx, ~15% 4xx, ~10% 5xx.
func RandomStatusCode(r *rand.Rand) int {
roll := r.Float64() // #nosec G404
switch {
case roll < 0.70:
return Status2xx.Random(r)
case roll < 0.75:
return Status3xx.Random(r)
case roll < 0.90:
return Status4xx.Random(r)
default:
return Status5xx.Random(r)
}
}
99 changes: 99 additions & 0 deletions internal/datagen/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package datagen

import (
"math/rand"
"testing"
)

func TestHTTPMethods(t *testing.T) {
expected := map[string]bool{
"GET": true, "POST": true, "PUT": true, "DELETE": true,
"PATCH": true, "HEAD": true, "OPTIONS": true,
}
for _, m := range Methods.All() {
if !expected[m] {
t.Errorf("unexpected HTTP method: %q", m)
}
}
if Methods.Len() != 7 {
t.Errorf("expected 7 methods, got %d", Methods.Len())
}
}

func TestHTTPProtocols(t *testing.T) {
if Protocols.Len() < 3 {
t.Errorf("expected at least 3 protocols, got %d", Protocols.Len())
}
}

func TestHTTPStatusPools(t *testing.T) {
if Status2xx.Len() < 3 {
t.Errorf("Status2xx has %d items, want at least 3", Status2xx.Len())
}
if Status3xx.Len() < 3 {
t.Errorf("Status3xx has %d items, want at least 3", Status3xx.Len())
}
if Status4xx.Len() < 5 {
t.Errorf("Status4xx has %d items, want at least 5", Status4xx.Len())
}
if Status5xx.Len() < 3 {
t.Errorf("Status5xx has %d items, want at least 3", Status5xx.Len())
}
}

func TestAPIPaths(t *testing.T) {
if APIPaths.Len() < 15 {
t.Errorf("APIPaths has %d items, want at least 15", APIPaths.Len())
}
}

func TestRefererDomains(t *testing.T) {
if RefererDomains.Len() < 5 {
t.Errorf("RefererDomains has %d items, want at least 5", RefererDomains.Len())
}
}

func TestRandomStatusCode(t *testing.T) {
r := rand.New(rand.NewSource(42))

counts := map[string]int{
"2xx": 0,
"3xx": 0,
"4xx": 0,
"5xx": 0,
}
for i := 0; i < 10000; i++ {
code := RandomStatusCode(r)
switch {
case code >= 200 && code < 300:
counts["2xx"]++
case code >= 300 && code < 400:
counts["3xx"]++
case code >= 400 && code < 500:
counts["4xx"]++
case code >= 500 && code < 600:
counts["5xx"]++
default:
t.Errorf("unexpected status code: %d", code)
}
}

// Verify rough distribution: 70% 2xx, 5% 3xx, 15% 4xx, 10% 5xx
total := 10000.0
if float64(counts["2xx"])/total < 0.60 {
t.Errorf("2xx should be ~70%%, got %.1f%%", float64(counts["2xx"])/total*100)
}
if float64(counts["5xx"])/total < 0.05 {
t.Errorf("5xx should be ~10%%, got %.1f%%", float64(counts["5xx"])/total*100)
}
}

func TestRandomStatusCodeDeterministic(t *testing.T) {
r1 := rand.New(rand.NewSource(99))
r2 := rand.New(rand.NewSource(99))
for i := 0; i < 50; i++ {
if RandomStatusCode(r1) != RandomStatusCode(r2) {
t.Fatal("same seed should produce same status codes")
}
}
}
Loading