package webrtc import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" ) // TestStatsHandler_EmptySubsystem verifies that GET /webrtc/stats returns // a well-formed JSON body with all-zero counts when no streams or peers // are active and no WHIP handler is linked. func TestStatsHandler_EmptySubsystem(t *testing.T) { h := NewHandler(newTestSubsystem(t), 0) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/webrtc/stats", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) if err := h.StatsHandler(c); err != nil { t.Fatalf("StatsHandler returned error: %v", err) } if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String()) } var stats WebRTCStats if err := json.Unmarshal(rec.Body.Bytes(), &stats); err != nil { t.Fatalf("invalid JSON: %v\nbody: %s", err, rec.Body.String()) } if stats.ActiveStreams != 0 { t.Errorf("ActiveStreams: want 0, got %d", stats.ActiveStreams) } if stats.ActivePeers != 0 { t.Errorf("ActivePeers: want 0, got %d", stats.ActivePeers) } if stats.ActivePublishers != 0 { t.Errorf("ActivePublishers: want 0, got %d", stats.ActivePublishers) } if stats.UDPPortsInUse != 0 { t.Errorf("UDPPortsInUse: want 0, got %d", stats.UDPPortsInUse) } } // TestStatsHandler_WithWHIPHandler verifies that SetWHIPHandler links the // WHIP publisher count into the stats response. func TestStatsHandler_WithWHIPHandler(t *testing.T) { sub := newTestSubsystem(t) h := NewHandler(sub, 0) // Link a real WHIPHandler so that StatsHandler calls PublisherCount(). wh := NewWHIPHandler(sub, 0) h.SetWHIPHandler(wh) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/webrtc/stats", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) if err := h.StatsHandler(c); err != nil { t.Fatalf("StatsHandler returned error: %v", err) } var stats WebRTCStats if err := json.Unmarshal(rec.Body.Bytes(), &stats); err != nil { t.Fatalf("invalid JSON: %v", err) } // With no active publishers the count should be 0 — validates the // link does not panic and that PublisherCount() is being called. if stats.ActivePublishers != 0 { t.Errorf("ActivePublishers: want 0, got %d", stats.ActivePublishers) } } // TestStatsHandler_NilSub verifies that a nil Subsystem (possible during // early wiring) does not panic and returns zeros. func TestStatsHandler_NilSub(t *testing.T) { h := NewHandler(nil, 0) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/webrtc/stats", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) if err := h.StatsHandler(c); err != nil { t.Fatalf("StatsHandler returned error: %v", err) } if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } var stats WebRTCStats if err := json.Unmarshal(rec.Body.Bytes(), &stats); err != nil { t.Fatalf("invalid JSON: %v", err) } if stats.ActiveStreams != 0 || stats.UDPPortsInUse != 0 { t.Errorf("expected all zeros with nil sub, got %+v", stats) } } // TestStatsHandler_JSONFieldNames verifies the JSON key names match the // contract defined in the issue so consumer scripts don't break. func TestStatsHandler_JSONFieldNames(t *testing.T) { h := NewHandler(newTestSubsystem(t), 0) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/webrtc/stats", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) if err := h.StatsHandler(c); err != nil { t.Fatalf("StatsHandler returned error: %v", err) } var raw map[string]interface{} if err := json.Unmarshal(rec.Body.Bytes(), &raw); err != nil { t.Fatalf("invalid JSON: %v", err) } for _, key := range []string{"active_streams", "active_peers", "active_publishers", "udp_ports_in_use"} { if _, ok := raw[key]; !ok { t.Errorf("JSON response missing required field %q", key) } } }