internal/metrics/metrics_test.go

package metrics

import (
	"honeypot/internal/types"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestSanitizeUTF8(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  string
	}{
		{"valid utf8", "hello", "hello"},
		{"invalid utf8", "hello\xffworld", "hello\ufffdworld"},
		{"empty", "", ""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := SanitizeUTF8(tt.input); got != tt.want {
				t.Errorf("SanitizeUTF8(%q) = %q, want %q", tt.input, got, tt.want)
			}
		})
	}
}

func TestNewMetricsCollector(t *testing.T) {
	mc := NewMetricsCollector(false)
	if mc == nil {
		t.Fatal("NewMetricsCollector returned nil")
	}
	if mc.packetsTotal == nil {
		t.Error("packetsTotal counter vec is nil")
	}
	if mc.fieldCounts == nil {
		t.Error("fieldCounts map is nil")
	}
}

func TestRecordEvent(t *testing.T) {
	mc := NewMetricsCollector(false)

	e := types.LogEvent{
		Type:       "ssh",
		Event:      types.EventAuthAttempt,
		RemoteAddr: "1.2.3.4",
		Fields:     map[string]interface{}{"username": "admin"},
	}

	mc.RecordEvent(e)

	mc.mu.RLock()
	defer mc.mu.RUnlock()

	if mc.fieldCounts["ssh"] == nil {
		t.Error("fieldCounts['ssh'] should not be nil")
	}
	if mc.fieldCounts["ssh"]["username"]["admin"] != 1 {
		t.Errorf("expected count 1 for ssh/username/admin, got %v", mc.fieldCounts["ssh"]["username"]["admin"])
	}
	if mc.fieldCounts["ssh"]["remote_addr"]["1.2.3.4"] != 1 {
		t.Errorf("expected count 1 for ssh/remote_addr/1.2.3.4, got %v", mc.fieldCounts["ssh"]["remote_addr"]["1.2.3.4"])
	}
}

func TestGetTopN(t *testing.T) {
	counts := map[string]int64{
		"a": 10,
		"b": 20,
		"c": 5,
		"d": 15,
	}

	top2 := getTopN(counts, 2)
	if len(top2) != 2 {
		t.Errorf("expected 2 items, got %v", len(top2))
	}
	if top2["b"] != 20 || top2["d"] != 15 {
		t.Errorf("unexpected top items: %v", top2)
	}

	top10 := getTopN(counts, 10)
	if len(top10) != 4 {
		t.Errorf("expected 4 items, got %v", len(top10))
	}
}

func TestGetHandler(t *testing.T) {
	mc := NewMetricsCollector(true) // disable HW metrics to avoid /proc access errors in some environments
	handler := mc.GetHandler()
	if handler == nil {
		t.Fatal("GetHandler returned nil")
	}

	req := httptest.NewRequest("GET", "/metrics", nil)
	rr := httptest.NewRecorder()
	handler.ServeHTTP(rr, req)

	if rr.Code != http.StatusOK {
		t.Errorf("expected status OK, got %v", rr.Code)
	}
}

func TestRegisterTopNField(t *testing.T) {
	mc := NewMetricsCollector(false)
	mc.RegisterTopNField("test_type", "test_field")

	mc.mu.RLock()
	registered := mc.registeredFields["test_type"]["test_field"]
	mc.mu.RUnlock()

	if !registered {
		t.Error("test_field for test_type was not registered")
	}
}