@@ -0,0 +1,330 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"91porn-server/common/hevcpull"
|
||||
"91porn-server/common/laosiji"
|
||||
"91porn-server/models/v/vidmod"
|
||||
"91porn-server/skd/skdg"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func TestBuildH265AppTranscodeM3u8URL(t *testing.T) {
|
||||
const pullSecret = "test-pull-secret-strong-32-bytes!!"
|
||||
now := time.Date(2026, 7, 25, 12, 0, 0, 0, time.UTC)
|
||||
expiresAt := now.Add(h265PullURLTTL)
|
||||
tests := []struct {
|
||||
name string
|
||||
base string
|
||||
source string
|
||||
wantPath string
|
||||
}{
|
||||
{
|
||||
name: "normalizes slashes",
|
||||
base: " https://app.example.com/ ",
|
||||
source: " /video/demo/index.m3u8 ",
|
||||
wantPath: "/api/app/vid/transcode/m3u8/video/demo/index.m3u8",
|
||||
},
|
||||
{name: "empty base", source: "video.m3u8"},
|
||||
{name: "empty source", base: "https://app.example.com"},
|
||||
{name: "base without scheme", base: "app.example.com", source: "video.m3u8"},
|
||||
{name: "plain http is forbidden", base: "http://app.example.com", source: "video.m3u8"},
|
||||
{name: "unsupported scheme", base: "ftp://app.example.com", source: "video.m3u8"},
|
||||
{name: "base with path", base: "https://app.example.com/prefix", source: "video.m3u8"},
|
||||
{name: "base with query", base: "https://app.example.com?token=x", source: "video.m3u8"},
|
||||
{name: "source traversal", base: "https://app.example.com", source: "video/../index.m3u8"},
|
||||
{name: "absolute source", base: "https://app.example.com", source: "https://cdn.example/index.m3u8"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := buildH265AppTranscodeM3u8URL(tt.base, tt.source, pullSecret, expiresAt)
|
||||
if tt.wantPath == "" {
|
||||
if got != "" {
|
||||
t.Fatalf("buildH265AppTranscodeM3u8URL() = %q, want empty", got)
|
||||
}
|
||||
return
|
||||
}
|
||||
parsed, err := url.Parse(got)
|
||||
if err != nil {
|
||||
t.Fatalf("parse signed URL: %v", err)
|
||||
}
|
||||
if parsed.Path != tt.wantPath {
|
||||
t.Fatalf("signed URL path = %q, want %q", parsed.Path, tt.wantPath)
|
||||
}
|
||||
if err = hevcpull.VerifyURL(parsed, pullSecret, now); err != nil {
|
||||
t.Fatalf("signed URL did not verify: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHevcAppTranscodeM3u8URLUsesStableClaimTime(t *testing.T) {
|
||||
oldConf := skdg.Conf
|
||||
t.Cleanup(func() {
|
||||
skdg.Conf = oldConf
|
||||
})
|
||||
claimedAt := time.Date(2026, 7, 25, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
skdg.Conf = nil
|
||||
if got := hevcAppTranscodeM3u8URL("video.m3u8", claimedAt); got != "" {
|
||||
t.Fatalf("nil config returned %q", got)
|
||||
}
|
||||
|
||||
skdg.Conf = &skdg.GlobalConfig{}
|
||||
skdg.Conf.Url.AppApiUrl = "https://app.example.com"
|
||||
skdg.Conf.Hevc.PullSecret = "test-pull-secret-strong-32-bytes!!"
|
||||
first := hevcAppTranscodeM3u8URL("video.m3u8", claimedAt)
|
||||
second := hevcAppTranscodeM3u8URL("video.m3u8", claimedAt)
|
||||
if first == "" || first != second {
|
||||
t.Fatalf("signed URL must be stable for one claim: first=%q second=%q", first, second)
|
||||
}
|
||||
parsed, err := url.Parse(first)
|
||||
if err != nil {
|
||||
t.Fatalf("parse signed URL: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(parsed.Path, "/api/app/vid/transcode/m3u8/") {
|
||||
t.Fatalf("unexpected signed URL path: %s", parsed.Path)
|
||||
}
|
||||
if err = hevcpull.VerifyURL(parsed, skdg.Conf.Hevc.PullSecret, claimedAt); err != nil {
|
||||
t.Fatalf("signed URL did not verify: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestH265CloudFileIDIsIndependentFromSignedPullURL(t *testing.T) {
|
||||
video := &vidmod.VideoModel{
|
||||
ID: primitive.NewObjectID(),
|
||||
SourceURL: " /sp/movie/index.m3u8 ",
|
||||
H265FailCount: 1,
|
||||
H265PendingAt: time.Date(2026, 7, 25, 12, 0, 0, 0, time.UTC),
|
||||
}
|
||||
first := h265CloudFileID(video)
|
||||
if first == "" {
|
||||
t.Fatal("empty cloud file ID")
|
||||
}
|
||||
|
||||
video.H265PendingAt = video.H265PendingAt.Add(time.Hour)
|
||||
video.SourceURL = "/sp/movie/index.m3u8"
|
||||
if got := h265CloudFileID(video); got != first {
|
||||
t.Fatalf("equivalent source or claim time changed cloud file ID: %q != %q", got, first)
|
||||
}
|
||||
|
||||
video.SourceURL = "/sp/other/index.m3u8"
|
||||
if got := h265CloudFileID(video); got == first {
|
||||
t.Fatal("different source reused cloud file ID")
|
||||
}
|
||||
video.SourceURL = "/sp/movie/index.m3u8"
|
||||
video.H265FailCount++
|
||||
if got := h265CloudFileID(video); got == first {
|
||||
t.Fatal("new retry attempt reused cloud file ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestH265TranscodingJobEnabledRequiresEnvironmentSwitchAndConfig(t *testing.T) {
|
||||
oldConf := skdg.Conf
|
||||
oldLSJConfig := laosiji.Config{
|
||||
AppID: laosiji.Appid,
|
||||
APIKey: laosiji.APIKey,
|
||||
APIUrl: laosiji.APIUrl,
|
||||
ImageYuan: laosiji.IMAGEYUAN,
|
||||
NoticeURL: laosiji.NoticeURL,
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
skdg.Conf = oldConf
|
||||
laosiji.InitConfig(oldLSJConfig)
|
||||
})
|
||||
|
||||
skdg.Conf = &skdg.GlobalConfig{}
|
||||
skdg.Conf.Base.Env = "prod"
|
||||
skdg.Conf.Url.AppApiUrl = "https://app.example.com"
|
||||
laosiji.InitConfig(laosiji.Config{})
|
||||
if h265TranscodingJobEnabled() {
|
||||
t.Fatal("job enabled without laosiji credentials")
|
||||
}
|
||||
|
||||
laosiji.InitConfig(laosiji.Config{
|
||||
AppID: "test-app",
|
||||
APIKey: "test-key",
|
||||
APIUrl: "https://laosiji.example.com",
|
||||
})
|
||||
skdg.Conf.Base.Env = "test"
|
||||
if h265TranscodingJobEnabled() {
|
||||
t.Fatal("job enabled in test without explicit switch")
|
||||
}
|
||||
|
||||
enabled := true
|
||||
skdg.Conf.Hevc.EnableTranscode = &enabled
|
||||
if h265TranscodingJobEnabled() {
|
||||
t.Fatal("job enabled in test without hevc.pullSecret")
|
||||
}
|
||||
|
||||
skdg.Conf.Hevc.PullSecret = "test-pull-secret-strong-32-bytes!!"
|
||||
skdg.Conf.Url.AppApiUrl = ""
|
||||
if h265TranscodingJobEnabled() {
|
||||
t.Fatal("job enabled without appApiUrl")
|
||||
}
|
||||
|
||||
skdg.Conf.Url.AppApiUrl = "https://app.example.com"
|
||||
if !h265TranscodingJobEnabled() {
|
||||
t.Fatal("job disabled in test with explicit switch and complete configuration")
|
||||
}
|
||||
|
||||
skdg.Conf.Base.Env = "prod"
|
||||
skdg.Conf.Hevc.EnableTranscode = nil
|
||||
if !h265TranscodingJobEnabled() {
|
||||
t.Fatal("job disabled with complete production configuration")
|
||||
}
|
||||
|
||||
disabled := false
|
||||
skdg.Conf.Hevc.EnableTranscode = &disabled
|
||||
if h265TranscodingJobEnabled() {
|
||||
t.Fatal("job enabled after explicit production kill switch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestH265PlayableURL(t *testing.T) {
|
||||
video := &vidmod.VideoModel{SourceURL: "sp/source/index.m3u8"}
|
||||
|
||||
if got := h265PlayableURL(video, laosiji.TranscodeResult{
|
||||
Done: true,
|
||||
NoNeed: true,
|
||||
HevcURL: "https://app.example.com/api/app/vid/h5/light/m3u8/sp/source/index.m3u8",
|
||||
}); got != video.SourceURL {
|
||||
t.Fatalf("no-need result returned %q, want original source %q", got, video.SourceURL)
|
||||
}
|
||||
|
||||
if got := h265PlayableURL(video, laosiji.TranscodeResult{
|
||||
Done: true,
|
||||
HevcURL: "https://cdn.example.com/m3m/transcoded/index.m3u8",
|
||||
}); got != "laosiji/m3m/transcoded/index.m3u8" {
|
||||
t.Fatalf("transcoded result returned %q", got)
|
||||
}
|
||||
|
||||
if got := h265PlayableURL(nil, laosiji.TranscodeResult{Done: true, NoNeed: true}); got != "" {
|
||||
t.Fatalf("nil video no-need result returned %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestH265PendingTimedOut(t *testing.T) {
|
||||
now := time.Date(2026, 7, 25, 12, 0, 0, 0, time.UTC)
|
||||
tests := []struct {
|
||||
name string
|
||||
video *vidmod.VideoModel
|
||||
want bool
|
||||
}{
|
||||
{name: "nil video"},
|
||||
{
|
||||
name: "not pending",
|
||||
video: &vidmod.VideoModel{
|
||||
H265Status: vidmod.H265StatusQueued,
|
||||
H265PendingAt: now.Add(-25 * time.Hour),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing claim time",
|
||||
video: &vidmod.VideoModel{
|
||||
H265Status: vidmod.H265StatusPending,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "still within timeout",
|
||||
video: &vidmod.VideoModel{
|
||||
H265Status: vidmod.H265StatusPending,
|
||||
H265PendingAt: now.Add(-h265PendingTimeout + time.Second),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "at timeout",
|
||||
video: &vidmod.VideoModel{
|
||||
H265Status: vidmod.H265StatusPending,
|
||||
H265PendingAt: now.Add(-h265PendingTimeout),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "past timeout",
|
||||
video: &vidmod.VideoModel{
|
||||
H265Status: vidmod.H265StatusPending,
|
||||
H265PendingAt: now.Add(-h265PendingTimeout - time.Second),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "future timestamp",
|
||||
video: &vidmod.VideoModel{
|
||||
H265Status: vidmod.H265StatusPending,
|
||||
H265PendingAt: now.Add(time.Second),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := h265PendingTimedOut(tt.video, now); got != tt.want {
|
||||
t.Fatalf("h265PendingTimedOut() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHomeH265QueryMatchesAppHomepageSorts(t *testing.T) {
|
||||
for _, sortType := range []int{1, 2, 3, 7, 9} {
|
||||
filter := homeH265Filter("module-id", sortType)
|
||||
if _, exists := filter["reviewAt"]; exists {
|
||||
t.Fatalf("sort %d unexpectedly limits reviewAt: %#v", sortType, filter["reviewAt"])
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
sortType int
|
||||
want bson.D
|
||||
}{
|
||||
{
|
||||
sortType: 1,
|
||||
want: bson.D{{Key: "reviewAt", Value: -1}},
|
||||
},
|
||||
{
|
||||
sortType: 2,
|
||||
want: bson.D{
|
||||
{Key: "liaoBaTopSort", Value: -1},
|
||||
{Key: "likeCount", Value: -1},
|
||||
{Key: "reviewAt", Value: -1},
|
||||
},
|
||||
},
|
||||
{
|
||||
sortType: 3,
|
||||
want: bson.D{
|
||||
{Key: "playCount", Value: -1},
|
||||
{Key: "reviewAt", Value: -1},
|
||||
},
|
||||
},
|
||||
{
|
||||
sortType: 7,
|
||||
want: bson.D{
|
||||
{Key: "collectCount", Value: -1},
|
||||
{Key: "reviewAt", Value: -1},
|
||||
},
|
||||
},
|
||||
{
|
||||
sortType: 9,
|
||||
want: bson.D{
|
||||
{Key: "hot", Value: -1},
|
||||
{Key: "reviewAt", Value: -1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := homeH265Options(tt.sortType).Sort
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("sort %d = %#v, want %#v", tt.sortType, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user