BuildArgs hardcoded -map 0✌️0 / -map 0🅰️0 for the two RTP legs. Correct for production RTMP/SRT publishers (single combined input), but breaks any process whose audio lives on a different input index — multi-input lavfi test scaffolds, multi-camera pipelines, SDI + file-audio mixes, etc. Adds VideoMap and AudioMap fields to ConfigWebRTC (and the API DTO), defaulting to the prior literals so existing deployments are unaffected. BuildArgs reads them. Tests: - TestBuildArgs_DefaultMaps locks the empty-string default behavior - TestBuildArgs_CustomMaps drives the multi-input override path - TestProcessConfigWebRTCMapsRoundtrip extends the DTO roundtrip Closes #2. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
109 lines
3.7 KiB
Go
109 lines
3.7 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)
|
|
}
|
|
}
|
|
|
|
// TestProcessConfigWebRTCMapsRoundtrip extends the WebRTC DTO
|
|
// roundtrip with the issue-#2 VideoMap/AudioMap fields so the
|
|
// regression doesn't repeat: a multi-input pipeline that sets
|
|
// `audio_map: "1:a:0"` must reach the restream config layer
|
|
// unchanged.
|
|
func TestProcessConfigWebRTCMapsRoundtrip(t *testing.T) {
|
|
body := []byte(`{
|
|
"id":"p","input":[{"id":"i","address":"x"}],"output":[{"id":"o","address":"-"}],
|
|
"webrtc":{"enabled":true,"video_map":"0:v:1","audio_map":"1:a:0"}
|
|
}`)
|
|
var dto ProcessConfig
|
|
if err := json.Unmarshal(body, &dto); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
if dto.WebRTC.VideoMap != "0:v:1" || dto.WebRTC.AudioMap != "1:a:0" {
|
|
t.Fatalf("DTO maps lost: %+v", dto.WebRTC)
|
|
}
|
|
cfg := dto.Marshal()
|
|
if cfg.WebRTC.VideoMap != "0:v:1" || cfg.WebRTC.AudioMap != "1:a:0" {
|
|
t.Fatalf("app.Config maps lost: %+v", cfg.WebRTC)
|
|
}
|
|
var back ProcessConfig
|
|
back.Unmarshal(cfg)
|
|
if back.WebRTC.VideoMap != "0:v:1" || back.WebRTC.AudioMap != "1:a:0" {
|
|
t.Fatalf("Unmarshal lost maps: %+v", back.WebRTC)
|
|
}
|
|
}
|