orama/pkg/gateway/handlers/webrtc/handlers_test.go
anonpenguin23 714a986a78 Bump version to 0.112.2 and update TURN server configuration
- Updated version in Makefile to 0.112.2.
- Enhanced SFU server error handling to ignore http.ErrServerClosed.
- Added TURNS (TURN over TLS) configuration options in TURN server and related components.
- Updated firewall rules to include TURNS ports and modified related tests.
- Implemented self-signed certificate generation for TURNS.
- Adjusted TURN server to support both UDP and TCP listeners.
- Updated WebRTC and SFU components to accommodate new TURNS configurations.
2026-02-23 16:32:32 +02:00

272 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,
"", // defaults to 127.0.0.1 in tests
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) != 3 {
t.Errorf("uris count = %v, want 3", 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")
}
}