orama/pkg/gateway/handlers/webrtc/handlers_test.go
anonpenguin23 8ee606bfb1 feat: implement SFU and TURN server functionality
- Add signaling package with message types and structures for SFU communication.
- Implement client and server message serialization/deserialization tests.
- Enhance systemd manager to handle SFU and TURN services, including start/stop logic.
- Create TURN server configuration and main server logic with HMAC-SHA1 authentication.
- Add tests for TURN server credential generation and validation.
- Define systemd service files for SFU and TURN services.
2026-02-21 11:17:13 +02:00

271 lines
7.3 KiB
Go

package webrtc
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
"github.com/DeBrosOfficial/network/pkg/logging"
)
func testHandlers() *WebRTCHandlers {
logger, _ := logging.NewColoredLogger(logging.ComponentGeneral, false)
return NewWebRTCHandlers(
logger,
8443,
"turn.ns-test.dbrs.space",
"test-secret-key-32bytes-long!!!!",
nil, // No actual proxy in tests
)
}
func requestWithNamespace(method, path, namespace string) *http.Request {
req := httptest.NewRequest(method, path, nil)
ctx := context.WithValue(req.Context(), ctxkeys.NamespaceOverride, namespace)
return req.WithContext(ctx)
}
// --- Credentials handler tests ---
func TestCredentialsHandler_Success(t *testing.T) {
h := testHandlers()
req := requestWithNamespace("POST", "/v1/webrtc/turn/credentials", "test-ns")
w := httptest.NewRecorder()
h.CredentialsHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
}
var result map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&result); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if result["username"] == nil || result["username"] == "" {
t.Error("expected non-empty username")
}
if result["password"] == nil || result["password"] == "" {
t.Error("expected non-empty password")
}
if result["ttl"] == nil {
t.Error("expected ttl field")
}
ttl, ok := result["ttl"].(float64)
if !ok || ttl != 600 {
t.Errorf("ttl = %v, want 600", result["ttl"])
}
uris, ok := result["uris"].([]interface{})
if !ok || len(uris) != 2 {
t.Errorf("uris count = %v, want 2", result["uris"])
}
}
func TestCredentialsHandler_MethodNotAllowed(t *testing.T) {
h := testHandlers()
req := requestWithNamespace("GET", "/v1/webrtc/turn/credentials", "test-ns")
w := httptest.NewRecorder()
h.CredentialsHandler(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("status = %d, want %d", w.Code, http.StatusMethodNotAllowed)
}
}
func TestCredentialsHandler_NoNamespace(t *testing.T) {
h := testHandlers()
req := httptest.NewRequest("POST", "/v1/webrtc/turn/credentials", nil)
w := httptest.NewRecorder()
h.CredentialsHandler(w, req)
if w.Code != http.StatusForbidden {
t.Errorf("status = %d, want %d", w.Code, http.StatusForbidden)
}
}
func TestCredentialsHandler_NoTURNSecret(t *testing.T) {
logger, _ := logging.NewColoredLogger(logging.ComponentGeneral, false)
h := NewWebRTCHandlers(logger, 8443, "turn.test.dbrs.space", "", nil)
req := requestWithNamespace("POST", "/v1/webrtc/turn/credentials", "test-ns")
w := httptest.NewRecorder()
h.CredentialsHandler(w, req)
if w.Code != http.StatusServiceUnavailable {
t.Errorf("status = %d, want %d", w.Code, http.StatusServiceUnavailable)
}
}
// --- Signal handler tests ---
func TestSignalHandler_NoNamespace(t *testing.T) {
h := testHandlers()
req := httptest.NewRequest("GET", "/v1/webrtc/signal", nil)
w := httptest.NewRecorder()
h.SignalHandler(w, req)
if w.Code != http.StatusForbidden {
t.Errorf("status = %d, want %d", w.Code, http.StatusForbidden)
}
}
func TestSignalHandler_NoSFUPort(t *testing.T) {
logger, _ := logging.NewColoredLogger(logging.ComponentGeneral, false)
h := NewWebRTCHandlers(logger, 0, "", "secret", nil)
req := requestWithNamespace("GET", "/v1/webrtc/signal", "test-ns")
w := httptest.NewRecorder()
h.SignalHandler(w, req)
if w.Code != http.StatusServiceUnavailable {
t.Errorf("status = %d, want %d", w.Code, http.StatusServiceUnavailable)
}
}
func TestSignalHandler_NoProxyFunc(t *testing.T) {
h := testHandlers() // proxyWebSocket is nil
req := requestWithNamespace("GET", "/v1/webrtc/signal", "test-ns")
w := httptest.NewRecorder()
h.SignalHandler(w, req)
if w.Code != http.StatusInternalServerError {
t.Errorf("status = %d, want %d", w.Code, http.StatusInternalServerError)
}
}
// --- Rooms handler tests ---
func TestRoomsHandler_MethodNotAllowed(t *testing.T) {
h := testHandlers()
req := requestWithNamespace("POST", "/v1/webrtc/rooms", "test-ns")
w := httptest.NewRecorder()
h.RoomsHandler(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("status = %d, want %d", w.Code, http.StatusMethodNotAllowed)
}
}
func TestRoomsHandler_NoNamespace(t *testing.T) {
h := testHandlers()
req := httptest.NewRequest("GET", "/v1/webrtc/rooms", nil)
w := httptest.NewRecorder()
h.RoomsHandler(w, req)
if w.Code != http.StatusForbidden {
t.Errorf("status = %d, want %d", w.Code, http.StatusForbidden)
}
}
func TestRoomsHandler_NoSFUPort(t *testing.T) {
logger, _ := logging.NewColoredLogger(logging.ComponentGeneral, false)
h := NewWebRTCHandlers(logger, 0, "", "secret", nil)
req := requestWithNamespace("GET", "/v1/webrtc/rooms", "test-ns")
w := httptest.NewRecorder()
h.RoomsHandler(w, req)
if w.Code != http.StatusServiceUnavailable {
t.Errorf("status = %d, want %d", w.Code, http.StatusServiceUnavailable)
}
}
func TestRoomsHandler_SFUProxySuccess(t *testing.T) {
// Start a mock SFU health endpoint
mockSFU := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok","rooms":3}`))
}))
defer mockSFU.Close()
// Extract port from mock server
logger, _ := logging.NewColoredLogger(logging.ComponentGeneral, false)
// Parse port from mockSFU.URL (format: http://127.0.0.1:PORT)
var port int
for i := len(mockSFU.URL) - 1; i >= 0; i-- {
if mockSFU.URL[i] == ':' {
p := mockSFU.URL[i+1:]
for _, c := range p {
port = port*10 + int(c-'0')
}
break
}
}
h := NewWebRTCHandlers(logger, port, "", "secret", nil)
req := requestWithNamespace("GET", "/v1/webrtc/rooms", "test-ns")
w := httptest.NewRecorder()
h.RoomsHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
}
body := w.Body.String()
if body != `{"status":"ok","rooms":3}` {
t.Errorf("body = %q, want %q", body, `{"status":"ok","rooms":3}`)
}
}
// --- Helper tests ---
func TestResolveNamespaceFromRequest(t *testing.T) {
// With namespace
req := requestWithNamespace("GET", "/test", "my-namespace")
ns := resolveNamespaceFromRequest(req)
if ns != "my-namespace" {
t.Errorf("namespace = %q, want %q", ns, "my-namespace")
}
// Without namespace
req = httptest.NewRequest("GET", "/test", nil)
ns = resolveNamespaceFromRequest(req)
if ns != "" {
t.Errorf("namespace = %q, want empty", ns)
}
}
func TestWriteError(t *testing.T) {
w := httptest.NewRecorder()
writeError(w, http.StatusBadRequest, "bad request")
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", w.Code, http.StatusBadRequest)
}
var result map[string]string
if err := json.NewDecoder(w.Body).Decode(&result); err != nil {
t.Fatalf("failed to decode: %v", err)
}
if result["error"] != "bad request" {
t.Errorf("error = %q, want %q", result["error"], "bad request")
}
}
func TestWriteJSON(t *testing.T) {
w := httptest.NewRecorder()
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
if w.Code != http.StatusOK {
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
}
if ct := w.Header().Get("Content-Type"); ct != "application/json" {
t.Errorf("Content-Type = %q, want %q", ct, "application/json")
}
}