datarhei-dragonfork-core/http/api/process_webrtc_test.go
Zac Gaetano f6d36bfa66
Some checks failed
tests / build (push) Failing after 3s
fix(http/api): carry process WebRTC config through the API DTO
ProcessConfig in http/api/process.go shipped without a WebRTC field, so
JSON arriving at POST /api/v3/process was silently stripped of
"webrtc":{"enabled":true}. Marshal() handed restream a zero
ConfigWebRTC, the OnProcessStart hook no-op'd, and every WHEP request
returned 404 — even with a running webrtc-enabled process.

Caught on the M2 TrueNAS deploy at acceptance time: GET /process/{id}/config
came back without the webrtc block, despite the inbound JSON having it.
This is the API-layer twin of the earlier 'fix(config): preserve WebRTC
section in Config.Clone()' — same class of bug (drop-on-copy), different
struct.

- Add ProcessConfigWebRTC mirroring app.ConfigWebRTC.
- Marshal: copy DTO -> app.Config.WebRTC.
- Unmarshal: copy app.Config.WebRTC -> DTO.
- Regression tests cover both the JSON->DTO->Config path and the
  default (no webrtc block) case.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 04:53:25 +00:00

81 lines
2.6 KiB
Go

package api
import (
"encoding/json"
"testing"
"github.com/datarhei/core/v16/restream/app"
)
// TestProcessConfigWebRTCRoundtrip locks down the API DTO ↔ restream
// app.Config mapping for the per-process WebRTC block.
//
// Regression: the M2 cut shipped without WebRTC on ProcessConfig, so
// JSON arriving at POST /api/v3/process was silently stripped of
// `webrtc.enabled`, the restream config never saw it, the start hook
// never bound a Source, and WHEP returned 404. This test fails on the
// pre-fix code (Marshal would yield `app.ConfigWebRTC{}`) and passes
// once the DTO carries the field.
func TestProcessConfigWebRTCRoundtrip(t *testing.T) {
// 1. JSON in → DTO → app.Config
body := []byte(`{
"id":"p","input":[{"id":"i","address":"x"}],"output":[{"id":"o","address":"-"}],
"webrtc":{"enabled":true,"video_pt":102,"audio_pt":111,"force_transcode":true}
}`)
var dto ProcessConfig
if err := json.Unmarshal(body, &dto); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if !dto.WebRTC.Enabled {
t.Fatalf("DTO.WebRTC.Enabled lost on JSON decode: %+v", dto.WebRTC)
}
cfg := dto.Marshal()
if !cfg.WebRTC.Enabled || cfg.WebRTC.VideoPT != 102 || cfg.WebRTC.AudioPT != 111 || !cfg.WebRTC.ForceTranscode {
t.Fatalf("app.Config.WebRTC mapped wrong: %+v", cfg.WebRTC)
}
// 2. app.Config → DTO → JSON out
stored := &app.Config{
ID: "p",
Input: []app.ConfigIO{{ID: "i", Address: "x"}},
Output: []app.ConfigIO{{ID: "o", Address: "-"}},
WebRTC: app.ConfigWebRTC{
Enabled: true,
VideoPT: 102,
AudioPT: 111,
ForceTranscode: true,
},
}
var dto2 ProcessConfig
dto2.Unmarshal(stored)
if !dto2.WebRTC.Enabled || dto2.WebRTC.VideoPT != 102 {
t.Fatalf("Unmarshal lost WebRTC: %+v", dto2.WebRTC)
}
out, err := json.Marshal(dto2)
if err != nil {
t.Fatalf("marshal: %v", err)
}
// Decode again and compare.
var dto3 ProcessConfig
if err := json.Unmarshal(out, &dto3); err != nil {
t.Fatalf("re-unmarshal: %v", err)
}
if dto3.WebRTC != dto.WebRTC {
t.Fatalf("roundtrip diverged: in=%+v out=%+v", dto.WebRTC, dto3.WebRTC)
}
}
// TestProcessConfigWebRTCDefaults: when "webrtc" is absent in the
// inbound JSON, Marshal must still produce a valid app.Config — the
// zero ConfigWebRTC means "disabled" and the start hook should no-op.
func TestProcessConfigWebRTCDefaults(t *testing.T) {
body := []byte(`{"id":"p","input":[{"id":"i","address":"x"}],"output":[{"id":"o","address":"-"}]}`)
var dto ProcessConfig
if err := json.Unmarshal(body, &dto); err != nil {
t.Fatalf("unmarshal: %v", err)
}
cfg := dto.Marshal()
if cfg.WebRTC.Enabled {
t.Fatalf("default should be disabled, got %+v", cfg.WebRTC)
}
}