Capture, decrypt, and restream toolkit with compiled-in app modules (RTE, TG4, BBC), on-device MITM proxy, streamd control plane, and www. BBC module.yaml is published (clear streams); other module values stay local.
129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
package tg4
|
|
|
|
import (
|
|
"testing"
|
|
|
|
appreg "drmdecryption/app"
|
|
"drmdecryption/modcfg"
|
|
"drmdecryption/modules/provider"
|
|
)
|
|
|
|
// Compile-time proof the plugin satisfies every interface the host expects.
|
|
var (
|
|
_ appreg.App = (*App)(nil)
|
|
_ appreg.Catalog = (*App)(nil)
|
|
_ appreg.StreamDefaults = (*App)(nil)
|
|
)
|
|
|
|
func testApp(t *testing.T) *App {
|
|
t.Helper()
|
|
cfg := Config{
|
|
AccountID: "acct",
|
|
PolicyKey: "pk",
|
|
PlaybackConfigURL: "https://cfg.test/playback.json",
|
|
Channels: map[string]Channel{
|
|
"main": {Label: "Main", VideoID: "111", TokenField: "mainToken", LiveCardIndex: 0},
|
|
"extra": {Label: "Extra", StreamIDField: "extraStreamId", TokenField: "extraToken", LiveCardIndex: 1},
|
|
"phone": {Label: "Phone only", PhoneOnly: true, LiveCardIndex: 3},
|
|
},
|
|
Common: provider.Common{
|
|
Aliases: map[string]string{"primary": "main", "second": "extra"},
|
|
},
|
|
}.withDefaults()
|
|
labels := map[string]string{}
|
|
for id, ch := range cfg.Channels {
|
|
labels[id] = ch.Label
|
|
}
|
|
return &App{Base: provider.New(Name, cfg.Common, labels, modcfg.Result{Path: "test"}), cfg: cfg}
|
|
}
|
|
|
|
func TestAliasesResolveFromConfig(t *testing.T) {
|
|
a := testApp(t)
|
|
for in, want := range map[string]string{
|
|
"main": "main", "MAIN": "main", "primary": "main",
|
|
"second": "extra", "extra": "extra",
|
|
} {
|
|
got, err := a.Base.Resolve(in)
|
|
if err != nil {
|
|
t.Fatalf("resolve %q: %v", in, err)
|
|
}
|
|
if got != want {
|
|
t.Errorf("resolve(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
if _, err := a.Base.Resolve("nope"); err == nil {
|
|
t.Error("unknown channel must error")
|
|
}
|
|
}
|
|
|
|
// A video id pinned in config wins; otherwise it is read from the named remote
|
|
// playback-config field. Both paths are data-driven, no channel name switch.
|
|
func TestResolveChannelReadsConfiguredFields(t *testing.T) {
|
|
a := testApp(t)
|
|
pc := playbackConfig{
|
|
"mainToken": "tok-main",
|
|
"extraToken": "tok-extra",
|
|
"extraStreamId": "222",
|
|
}
|
|
_, vid, tok, err := a.resolveChannel(pc, "main")
|
|
if err != nil {
|
|
t.Fatalf("main: %v", err)
|
|
}
|
|
if vid != "111" || tok != "tok-main" {
|
|
t.Errorf("main → video %q token %q", vid, tok)
|
|
}
|
|
_, vid, tok, err = a.resolveChannel(pc, "extra")
|
|
if err != nil {
|
|
t.Fatalf("extra: %v", err)
|
|
}
|
|
if vid != "222" || tok != "tok-extra" {
|
|
t.Errorf("extra → video %q token %q", vid, tok)
|
|
}
|
|
}
|
|
|
|
// A phone-only channel must say so rather than fail with a confusing catalog error.
|
|
func TestPhoneOnlyChannelExplainsItself(t *testing.T) {
|
|
a := testApp(t)
|
|
_, _, _, err := a.resolveChannel(playbackConfig{}, "phone")
|
|
if err == nil {
|
|
t.Fatal("phone-only channel must not resolve from the catalog")
|
|
}
|
|
if want := "drm capture"; !contains(err.Error(), want) {
|
|
t.Errorf("error %q should point at %q", err, want)
|
|
}
|
|
}
|
|
|
|
func TestPlaybackConfigCoercesNumericIDs(t *testing.T) {
|
|
pc := playbackConfig{"a": "123", "b": float64(456), "c": true}
|
|
if pc.str("a") != "123" {
|
|
t.Error("string field")
|
|
}
|
|
if pc.str("b") != "456" {
|
|
t.Errorf("numeric field = %q, want 456", pc.str("b"))
|
|
}
|
|
if pc.str("missing") != "" || pc.str("") != "" {
|
|
t.Error("missing field must be empty")
|
|
}
|
|
if !pc.Flags()["c"] {
|
|
t.Error("bool flags should be reported")
|
|
}
|
|
}
|
|
|
|
// No provider identity may be compiled in.
|
|
func TestNoHardcodedCredentials(t *testing.T) {
|
|
cfg := Config{}.withDefaults()
|
|
if cfg.AccountID != "" || cfg.PolicyKey != "" || cfg.PlaybackConfigURL != "" || cfg.Package != "" {
|
|
t.Fatal("account id / policy key / config URL / package must come from module.yaml")
|
|
}
|
|
}
|
|
|
|
func contains(s, sub string) bool {
|
|
return len(s) >= len(sub) && (func() bool {
|
|
for i := 0; i+len(sub) <= len(s); i++ {
|
|
if s[i:i+len(sub)] == sub {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
})()
|
|
}
|