216 lines
7.0 KiB
Go
216 lines
7.0 KiB
Go
package metrics
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/glebarez/sqlite"
|
|
"gorm.io/gorm"
|
|
|
|
"chat/src/auth"
|
|
"chat/src/db"
|
|
"chat/src/ws"
|
|
)
|
|
|
|
func TestHandleHealth(t *testing.T) {
|
|
handler := HandleHealth()
|
|
req := httptest.NewRequest("GET", "/health", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", w.Code)
|
|
}
|
|
|
|
body := w.Body.String()
|
|
if !strings.Contains(body, `"healthy"`) {
|
|
t.Errorf("body missing healthy: %s", body)
|
|
}
|
|
if !strings.Contains(body, `"chat"`) {
|
|
t.Errorf("body missing service name: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestHandleNotFound(t *testing.T) {
|
|
handler := HandleNotFound()
|
|
req := httptest.NewRequest("GET", "/nonexistent", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("status = %d, want 404", w.Code)
|
|
}
|
|
|
|
body := w.Body.String()
|
|
if !strings.Contains(body, `"not found"`) {
|
|
t.Errorf("body missing not found: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestHandleMetrics(t *testing.T) {
|
|
chatRequestDurationHistogram = newRequestDurationHistogram(requestDurationBuckets)
|
|
ObserveRequestDuration(100 * time.Millisecond)
|
|
ObserveRequestDuration(750 * time.Millisecond)
|
|
|
|
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("failed to open test DB: %v", err)
|
|
}
|
|
gormDB.AutoMigrate(&db.Message{}, &db.ModerationLog{})
|
|
|
|
// Add some test data
|
|
gormDB.Create(&db.Message{Room: "main", Username: "u1", Text: "hi", Timestamp: time.Now(), Type: "public"})
|
|
gormDB.Create(&db.ModerationLog{Action: "mute", Target: "u2", Moderator: "admin", Reason: "spam", Timestamp: time.Now()})
|
|
|
|
sessionStore := auth.NewSessionStore()
|
|
hub := ws.NewHub(gormDB, nil, "test-instance", 10, 10*time.Second, "admin")
|
|
hub.DMsSent = 5
|
|
|
|
handler := HandleMetrics(gormDB, nil, hub, sessionStore, "sqlite", "test-instance")
|
|
req := httptest.NewRequest("GET", "/metrics", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", w.Code)
|
|
}
|
|
|
|
body := w.Body.String()
|
|
|
|
// Check required metric names
|
|
requiredMetrics := []string{
|
|
"linearvoid_chat_connected_clients",
|
|
"linearvoid_chat_messages_total",
|
|
"linearvoid_chat_dms_total",
|
|
"linearvoid_chat_moderation_actions_total",
|
|
"linearvoid_chat_redis_enabled",
|
|
"linearvoid_chat_redis_up",
|
|
"linearvoid_chat_redis_connected_clients",
|
|
"linearvoid_chat_redis_memory_used_bytes",
|
|
"linearvoid_chat_redis_memory_max_bytes",
|
|
"linearvoid_chat_redis_memory_usage_ratio",
|
|
"linearvoid_chat_admin_sessions_active",
|
|
"linearvoid_chat_build_info",
|
|
"linearvoid_chat_request_duration_seconds",
|
|
}
|
|
|
|
for _, metric := range requiredMetrics {
|
|
if !strings.Contains(body, metric) {
|
|
t.Errorf("body missing metric %s", metric)
|
|
}
|
|
}
|
|
|
|
// Check build_info has instance_id label
|
|
if !strings.Contains(body, `instance_id="test-instance"`) {
|
|
t.Error("build_info missing instance_id label")
|
|
}
|
|
|
|
// Check values
|
|
if !strings.Contains(body, "linearvoid_chat_messages_total 1") {
|
|
t.Error("messages_total should be 1")
|
|
}
|
|
if !strings.Contains(body, "linearvoid_chat_moderation_actions_total 1") {
|
|
t.Error("moderation_actions_total should be 1")
|
|
}
|
|
if !strings.Contains(body, "linearvoid_chat_dms_total 5") {
|
|
t.Error("dms_total should be 5")
|
|
}
|
|
if !strings.Contains(body, "linearvoid_chat_redis_enabled 0") {
|
|
t.Error("redis_enabled should be 0 (nil redis)")
|
|
}
|
|
if !strings.Contains(body, "linearvoid_chat_request_duration_seconds_bucket{le=\"1\"} 2") {
|
|
t.Error("request duration histogram should include both observed samples by the 1s bucket")
|
|
}
|
|
if !strings.Contains(body, "linearvoid_chat_request_duration_seconds_count 2") {
|
|
t.Error("request duration count should be 2")
|
|
}
|
|
}
|
|
|
|
func TestHandleMetrics_WrongMethod(t *testing.T) {
|
|
handler := HandleMetrics(nil, nil, nil, nil, "", "")
|
|
req := httptest.NewRequest("POST", "/metrics", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Errorf("status = %d, want 405", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestHandleMetrics_WithRedis(t *testing.T) {
|
|
chatRequestDurationHistogram = newRequestDurationHistogram(requestDurationBuckets)
|
|
ObserveRequestDuration(250 * time.Millisecond)
|
|
|
|
gormDB, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
gormDB.AutoMigrate(&db.Message{}, &db.ModerationLog{})
|
|
|
|
// mockRedis for metrics test
|
|
mr := &metricsMockRedis{}
|
|
sessionStore := auth.NewSessionStore()
|
|
hub := ws.NewHub(gormDB, mr, "test-instance", 10, 10*time.Second, "admin")
|
|
|
|
handler := HandleMetrics(gormDB, mr, hub, sessionStore, "sqlite", "inst-123")
|
|
req := httptest.NewRequest("GET", "/metrics", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", w.Code)
|
|
}
|
|
|
|
body := w.Body.String()
|
|
if !strings.Contains(body, "linearvoid_chat_redis_enabled 1") {
|
|
t.Error("redis_enabled should be 1")
|
|
}
|
|
if !strings.Contains(body, "linearvoid_chat_redis_up 1") {
|
|
t.Error("redis_up should be 1")
|
|
}
|
|
if !strings.Contains(body, "linearvoid_chat_redis_connected_clients 5") {
|
|
t.Error("redis_connected_clients should be 5")
|
|
}
|
|
if !strings.Contains(body, "linearvoid_chat_redis_memory_used_bytes 1048576") {
|
|
t.Error("redis_memory_used_bytes should be 1048576")
|
|
}
|
|
if !strings.Contains(body, "linearvoid_chat_redis_memory_usage_ratio 1") {
|
|
t.Error("redis_memory_usage_ratio should be 1 when maxmemory is unavailable or equal to used memory in tests")
|
|
}
|
|
if !strings.Contains(body, "linearvoid_chat_request_duration_seconds_count 1") {
|
|
t.Error("request duration count should be 1")
|
|
}
|
|
}
|
|
|
|
// metricsMockRedis is a minimal mock for metrics tests.
|
|
type metricsMockRedis struct{}
|
|
|
|
func (m *metricsMockRedis) Ping(ctx context.Context) error { return nil }
|
|
func (m *metricsMockRedis) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
|
|
return nil
|
|
}
|
|
func (m *metricsMockRedis) Get(ctx context.Context, key string) (string, error) { return "", nil }
|
|
func (m *metricsMockRedis) Del(ctx context.Context, keys ...string) error { return nil }
|
|
func (m *metricsMockRedis) Incr(ctx context.Context, key string) (int64, error) { return 0, nil }
|
|
func (m *metricsMockRedis) Decr(ctx context.Context, key string) (int64, error) { return 0, nil }
|
|
func (m *metricsMockRedis) Expire(ctx context.Context, key string, expiration time.Duration) error {
|
|
return nil
|
|
}
|
|
func (m *metricsMockRedis) Exists(ctx context.Context, keys ...string) (int64, error) { return 0, nil }
|
|
func (m *metricsMockRedis) Publish(ctx context.Context, channel string, message interface{}) error {
|
|
return nil
|
|
}
|
|
func (m *metricsMockRedis) PSubscribe(ctx context.Context, channels ...string) db.PubSub { return nil }
|
|
func (m *metricsMockRedis) Scan(ctx context.Context, cursor uint64, match string, count int64) ([]string, uint64, error) {
|
|
return nil, 0, nil
|
|
}
|
|
func (m *metricsMockRedis) Info(ctx context.Context, sections ...string) (string, error) {
|
|
return "connected_clients:5\nused_memory_human:1M\nmaxmemory_human:1M\n", nil
|
|
}
|
|
func (m *metricsMockRedis) Close() error { return nil } |