mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 03:26:37 +08:00
135 lines
4.6 KiB
Go
135 lines
4.6 KiB
Go
package handler
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"sync/atomic"
|
||
"testing"
|
||
|
||
"go.uber.org/zap"
|
||
|
||
"github.com/truewhile/MeBox/internal/model"
|
||
"github.com/truewhile/MeBox/internal/service"
|
||
)
|
||
|
||
type segmentPayload struct {
|
||
Segments []struct {
|
||
Kind string `json:"kind"`
|
||
StartMs int64 `json:"start_ms"`
|
||
EndMs int64 `json:"end_ms"`
|
||
} `json:"segments"`
|
||
AutoSkip bool `json:"auto_skip"`
|
||
}
|
||
|
||
const segmentsProviderBody = `{"tmdb_id":27205,"type":"movie","intro":[{"start_ms":null,"end_ms":38000}],"credits":[{"start_ms":6480000,"end_ms":null}]}`
|
||
|
||
func TestPlaybackSegmentsReturnsProviderDataAndAutoSkip(t *testing.T) {
|
||
router, svc, secret := newPlaybackScopeTestRouter(t)
|
||
// 片段数据与播放来源无关,云盘媒体同样适用,只要它能解析出外部 ID。
|
||
// 注意列名是 tm_db_id(GORM 对 TMDbID 的默认命名)。
|
||
if err := svc.Repo.DB.Model(&model.Media{}).
|
||
Where("id = ?", "media-1").Update("tm_db_id", 27205).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
var calls int32
|
||
provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
atomic.AddInt32(&calls, 1)
|
||
w.Header().Set("Content-Type", "application/json")
|
||
_, _ = w.Write([]byte(segmentsProviderBody))
|
||
}))
|
||
defer provider.Close()
|
||
svc.Segments = service.NewMediaSegmentService(zap.NewNop(), svc.Repo).
|
||
SetIntroDB(service.NewIntroDBService(zap.NewNop()).SetBaseURL(provider.URL))
|
||
|
||
// 默认档案打开「自动跳过片头」,接口应把开关原样带出来。
|
||
if err := svc.Repo.DB.Create(&model.PlayProfile{
|
||
Base: model.Base{ID: "profile-1"},
|
||
UserID: "user-1",
|
||
Name: "主档案",
|
||
IsDefault: true,
|
||
SkipIntro: true,
|
||
}).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
loginToken := signedTestToken(t, secret)
|
||
fetch := func() segmentPayload {
|
||
t.Helper()
|
||
req := httptest.NewRequest(http.MethodGet, "http://nas.local/api/playback/media-1/segments", nil)
|
||
req.Header.Set("Authorization", "Bearer "+loginToken)
|
||
w := httptest.NewRecorder()
|
||
router.ServeHTTP(w, req)
|
||
if w.Code != http.StatusOK {
|
||
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
|
||
}
|
||
var payload segmentPayload
|
||
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
||
t.Fatalf("decode: %v", err)
|
||
}
|
||
return payload
|
||
}
|
||
|
||
first := fetch()
|
||
if !first.AutoSkip {
|
||
t.Fatal("auto_skip should reflect the active profile's skip_intro switch")
|
||
}
|
||
if len(first.Segments) != 2 {
|
||
t.Fatalf("segments = %#v, want 2", first.Segments)
|
||
}
|
||
// start_ms: null -> 0;end_ms: null -> 0(延续到片尾,由客户端按时长补齐)。
|
||
if first.Segments[0].Kind != "intro" || first.Segments[0].StartMs != 0 || first.Segments[0].EndMs != 38_000 {
|
||
t.Fatalf("intro segment = %#v", first.Segments[0])
|
||
}
|
||
if first.Segments[1].Kind != "credits" || first.Segments[1].StartMs != 6_480_000 || first.Segments[1].EndMs != 0 {
|
||
t.Fatalf("credits segment = %#v", first.Segments[1])
|
||
}
|
||
|
||
// 第二次播放必须走本地缓存,不再打外网。
|
||
if second := fetch(); len(second.Segments) != 2 {
|
||
t.Fatalf("second fetch segments = %#v", second.Segments)
|
||
}
|
||
if got := atomic.LoadInt32(&calls); got != 1 {
|
||
t.Fatalf("provider calls = %d, want 1", got)
|
||
}
|
||
}
|
||
|
||
func TestPlaybackSegmentsAutoSkipIsFalseWithoutProfile(t *testing.T) {
|
||
router, svc, secret := newPlaybackScopeTestRouter(t)
|
||
svc.Segments = service.NewMediaSegmentService(zap.NewNop(), svc.Repo)
|
||
|
||
req := httptest.NewRequest(http.MethodGet, "http://nas.local/api/playback/media-1/segments", nil)
|
||
req.Header.Set("Authorization", "Bearer "+signedTestToken(t, secret))
|
||
w := httptest.NewRecorder()
|
||
router.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusOK {
|
||
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
|
||
}
|
||
var payload segmentPayload
|
||
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
||
t.Fatalf("decode: %v", err)
|
||
}
|
||
if payload.AutoSkip {
|
||
t.Fatal("auto_skip must default to false")
|
||
}
|
||
// 即使一条片段都没有,也必须返回空数组而不是 null,前端才能无条件遍历。
|
||
if payload.Segments == nil {
|
||
t.Fatal("segments must serialise as an empty array, not null")
|
||
}
|
||
}
|
||
|
||
func TestPlaybackSegmentsForUnknownMediaReturnsNotFound(t *testing.T) {
|
||
router, svc, secret := newPlaybackScopeTestRouter(t)
|
||
svc.Segments = service.NewMediaSegmentService(zap.NewNop(), svc.Repo)
|
||
|
||
req := httptest.NewRequest(http.MethodGet, "http://nas.local/api/playback/does-not-exist/segments", nil)
|
||
req.Header.Set("Authorization", "Bearer "+signedTestToken(t, secret))
|
||
w := httptest.NewRecorder()
|
||
router.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusNotFound {
|
||
t.Fatalf("status = %d body=%s, want 404", w.Code, w.Body.String())
|
||
}
|
||
}
|