//go:build live // Live tests hit the real origin and need this module's local module.yaml. // They are excluded from the normal test run; enable with: // // go -C apps/modules test -tags live ./rte/ -v package rte import ( "regexp" "strconv" "strings" "testing" "drmdecryption/mpd" ) // The rewriter must publish a usable manifest from the KID alone — this is the // path taken when the ad-stitched upstream session has expired (HTTP 410) and all // that is left is the key from an earlier capture. func TestLiveRewriteFromKIDOnly(t *testing.T) { cfg, res, err := loadConfig() if err != nil { t.Fatal(err) } if !res.Loaded { t.Skipf("no local values at %s", res.Path) } if len(cfg.KIDs) == 0 { t.Skip("no kids configured") } for channel, kid := range cfg.KIDs { t.Run(channel, func(t *testing.T) { resetCaches(channel) r := NewRewriterWith(cfg) // Empty upstream = expired session; only the KID hint is available. out, err := r.Rewrite(nil, kid) if err != nil { t.Fatalf("rewrite from KID: %v", err) } got := string(out) origin := cfg.Origins[channel] if origin == "" { t.Fatalf("no origin configured for %s", channel) } for _, want := range []string{ `type="dynamic"`, origin + cfg.Synthetic.SegmentPathSuffix, channel + "-" + cfg.Synthetic.VideoName, channel + "-" + cfg.Synthetic.AudioName, } { if !strings.Contains(got, want) { t.Errorf("manifest missing %q", want) } } u, _ := mpd.KIDToUUID(kid) if !strings.Contains(got, `cenc:default_KID="`+u+`"`) { t.Errorf("manifest does not carry KID %s", u) } // The timeline must be non-empty and on the encoder grid, or the // downloader asks the origin for segments that do not exist. re := regexp.MustCompile(``) ms := re.FindAllStringSubmatch(got, -1) if len(ms) != 2 { t.Fatalf("want 2 timelines, got %d", len(ms)) } grid, err := mpd.LearnGrid(channel, origin, cfg.Synthetic.GridSpec()) if err != nil { t.Fatalf("learn grid: %v", err) } for i, m := range ms { start, _ := strconv.ParseInt(m[1], 10, 64) dur, _ := strconv.ParseInt(m[2], 10, 64) rep, _ := strconv.Atoi(m[3]) wantDur, mod := grid.VDur, grid.VMod if i == 1 { wantDur, mod = grid.ADur, grid.AMod } if dur != wantDur { t.Errorf("timeline %d: d=%d, want %d (from the live origin)", i, dur, wantDur) } if int64(rep+1) != cfg.Synthetic.WindowSegments { t.Errorf("timeline %d: %d segments, want %d", i, rep+1, cfg.Synthetic.WindowSegments) } if off := ((start-mod)%wantDur + wantDur) % wantDur; off != 0 { t.Errorf("timeline %d: start %d is off the encoder grid by %d", i, start, off) } if start <= 0 { t.Errorf("timeline %d: non-positive start %d", i, start) } } t.Logf("%s: %d bytes, vdur=%d adur=%d", channel, len(out), grid.VDur, grid.ADur) }) } } // The KID→channel→origin lookup is what makes the expired-session path work. func TestLiveKIDResolvesToConfiguredOrigin(t *testing.T) { cfg, res, err := loadConfig() if err != nil { t.Fatal(err) } if !res.Loaded { t.Skipf("no local values at %s", res.Path) } r := NewRewriterWith(cfg) for channel, kid := range cfg.KIDs { gotCh, gotBase, ok := r.ResolveKID(mpd.KIDHex(kid)) if !ok { t.Errorf("%s: KID %s did not resolve", channel, kid) continue } if gotCh != channel { t.Errorf("KID %s resolved to %q, want %q", kid, gotCh, channel) } if gotBase != cfg.Origins[channel] { t.Errorf("%s: origin %q, want %q", channel, gotBase, cfg.Origins[channel]) } } } func resetCaches(channel string) { // The grid/anchor caches live in pkg/mpd and are keyed by channel; a fresh // process per test run is enough, so nothing to do here beyond documenting it. _ = channel }