datarhei-dragonfork-core/app/webrtc/handler_test.go
Zac Gaetano f6d5b3378a feat(webrtc): add Echo WHEP handler for app/webrtc subsystem
Introduces the HTTP surface the browser (or OBS WebRTC clients)
target when subscribing to a process's egress:

  POST   /whep/:id              -> answer SDP + Location header
  DELETE /whep/:id/:resource    -> tear down a specific peer

The handler looks up the per-process stream pair via the Subsystem,
validates SDP offer shape, and delegates peer creation to the core
PeerFactory's CreatePeerFromSources (two-source forwarding).

WHEP routes are left unauthenticated in M2 — browsers and OBS don't
carry the Core JWT, and per-process signed-URL tokens are an M3
enhancement. Deployments should place the endpoint behind an
authenticated reverse-proxy for now.

Tests cover:
  - 404 for POSTs against unregistered streams
  - 400 for empty/invalid SDP offers once a stream is registered
  - 404 for DELETE against unknown resource ids
2026-04-17 10:03:24 -04:00

89 lines
2.5 KiB
Go

package webrtc
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/labstack/echo/v4"
"github.com/datarhei/core/v16/config"
)
func newTestSubsystem(t *testing.T) *Subsystem {
t.Helper()
s, err := New(config.DataWebRTC{Enable: true}, nil)
if err != nil {
t.Fatalf("New: %v", err)
}
return s
}
// TestHandler_Subscribe_404WhenStreamMissing verifies the WHEP POST
// returns 404 when no process has registered a stream for that id.
func TestHandler_Subscribe_404WhenStreamMissing(t *testing.T) {
h := NewHandler(newTestSubsystem(t), 0)
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")
if err := h.Subscribe(c); err != nil {
t.Fatalf("Subscribe returned error: %v", err)
}
if rec.Code != http.StatusNotFound {
t.Fatalf("expected 404, got %d: %s", rec.Code, rec.Body.String())
}
}
// TestHandler_Subscribe_400OnEmptyBody verifies invalid SDP offers
// short-circuit before any peer is created. Requires a registered
// stream so lookup doesn't 404 first.
func TestHandler_Subscribe_400OnEmptyBody(t *testing.T) {
sub := newTestSubsystem(t)
// Register a dummy stream so the handler reaches body validation.
sub.mu.Lock()
sub.streams["probe"] = &processStream{id: "probe"} // video/audio nil is fine here — we never get past body parse
sub.mu.Unlock()
h := NewHandler(sub, 0)
e := echo.New()
req := httptest.NewRequest(http.MethodPost, "/whep/probe", strings.NewReader(""))
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetParamNames("id")
c.SetParamValues("probe")
if err := h.Subscribe(c); err != nil {
t.Fatalf("Subscribe returned error: %v", err)
}
if rec.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d: %s", rec.Code, rec.Body.String())
}
}
// TestHandler_Unsubscribe_404WhenUnknown verifies a DELETE with an
// unknown resource id returns 404 and no state mutation.
func TestHandler_Unsubscribe_404WhenUnknown(t *testing.T) {
h := NewHandler(newTestSubsystem(t), 0)
e := echo.New()
req := httptest.NewRequest(http.MethodDelete, "/whep/id/unknown", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetParamNames("id", "resource")
c.SetParamValues("id", "unknown")
if err := h.Unsubscribe(c); err != nil {
t.Fatalf("Unsubscribe returned error: %v", err)
}
if rec.Code != http.StatusNotFound {
t.Fatalf("expected 404, got %d", rec.Code)
}
}