diff --git a/app/webrtc/metrics_test.go b/app/webrtc/metrics_test.go new file mode 100644 index 0000000..9edcc05 --- /dev/null +++ b/app/webrtc/metrics_test.go @@ -0,0 +1,149 @@ +package webrtc + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/labstack/echo/v4" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" + + "github.com/datarhei/core/v16/config" +) + +func newTestHandler(t *testing.T) (*Handler, *prometheus.Registry) { + t.Helper() + s, err := New(config.DataWebRTC{Enable: true}, nil) + if err != nil { + t.Fatalf("New: %v", err) + } + h := NewHandler(s, 0) + reg := prometheus.NewRegistry() + h.InitMetrics(reg, "test") + return h, reg +} + +// TestMetrics_Subscribe404BumpsCounter checks that a 404 on unknown stream +// increments the request counter with the correct labels. +func TestMetrics_Subscribe404BumpsCounter(t *testing.T) { + h, reg := newTestHandler(t) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/whep/ghost", strings.NewReader("v=0\r\n")) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + c.SetParamNames("id") + c.SetParamValues("ghost") + _ = h.Subscribe(c) + + if rec.Code != http.StatusNotFound { + t.Fatalf("expected 404, got %d", rec.Code) + } + + if err := testutil.GatherAndCompare(reg, strings.NewReader(` +# HELP dragonfork_webrtc_whep_requests_total Count of WHEP HTTP requests by route, HTTP status code, and stream. +# TYPE dragonfork_webrtc_whep_requests_total counter +dragonfork_webrtc_whep_requests_total{code="404",core="test",route="subscribe",stream_id="ghost"} 1 +`), "dragonfork_webrtc_whep_requests_total"); err != nil { + t.Fatal(err) + } +} + +// TestMetrics_GlobalCapBumpsCapRejection checks that a global cap 503 fires +// the cap_rejections counter with scope=global. +func TestMetrics_GlobalCapBumpsCapRejection(t *testing.T) { + s, err := New(config.DataWebRTC{Enable: true}, nil) + if err != nil { + t.Fatalf("New: %v", err) + } + // maxPeers=1, but inject a stream so we get past lookup + s.mu.Lock() + s.streams["mystream"] = &processStream{id: "mystream"} + s.mu.Unlock() + + h := NewHandlerWithCaps(s, 1, 0) + reg := prometheus.NewRegistry() + h.InitMetrics(reg, "test") + // Force count to be at cap + h.count = 1 + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/whep/mystream", strings.NewReader("v=0\r\n")) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + c.SetParamNames("id") + c.SetParamValues("mystream") + _ = h.Subscribe(c) + + if rec.Code != http.StatusServiceUnavailable { + t.Fatalf("expected 503, got %d", rec.Code) + } + + n := testutil.ToFloat64(h.met.capRejections.WithLabelValues("", "global")) + if n != 1 { + t.Fatalf("cap_rejections{scope=global}: want 1, got %v", n) + } +} + +// TestMetrics_CodecMismatchBumpsCounter checks that a 406 SDP with no H264 +// increments codec_mismatches{kind=h264}. +func TestMetrics_CodecMismatchBumpsCounter(t *testing.T) { + s, err := New(config.DataWebRTC{Enable: true}, nil) + if err != nil { + t.Fatalf("New: %v", err) + } + s.mu.Lock() + s.streams["cam"] = &processStream{id: "cam"} + s.mu.Unlock() + + h := NewHandler(s, 0) + reg := prometheus.NewRegistry() + h.InitMetrics(reg, "test") + + // SDP with Opus but no H264 + sdp := "v=0\r\na=rtpmap:111 opus/48000/2\r\n" + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/whep/cam", strings.NewReader(sdp)) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + c.SetParamNames("id") + c.SetParamValues("cam") + _ = h.Subscribe(c) + + if rec.Code != http.StatusNotAcceptable { + t.Fatalf("expected 406, got %d", rec.Code) + } + + n := testutil.ToFloat64(h.met.codecMismatches.WithLabelValues("cam", "h264")) + if n != 1 { + t.Fatalf("codec_mismatches{kind=h264}: want 1, got %v", n) + } +} + +// TestMetrics_Stats returns consistent snapshots at zero and with streams. +func TestMetrics_Stats(t *testing.T) { + h, _ := newTestHandler(t) + + got := h.Stats() + if got.StreamCount != 0 { + t.Fatalf("expected 0 streams, got %d", got.StreamCount) + } + if got.UDPPortsInUse != 0 { + t.Fatalf("expected 0 udp ports, got %d", got.UDPPortsInUse) + } + + // Inject a stream to verify counts update + h.sub.mu.Lock() + h.sub.streams["test"] = &processStream{id: "test"} + h.sub.mu.Unlock() + + got = h.Stats() + if got.StreamCount != 1 { + t.Fatalf("expected 1 stream, got %d", got.StreamCount) + } + if got.UDPPortsInUse != 2 { + t.Fatalf("expected 2 udp ports, got %d", got.UDPPortsInUse) + } +}