mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
241 lines
7.4 KiB
Go
241 lines
7.4 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/truewhile/MeBox/internal/config"
|
|
"github.com/truewhile/MeBox/internal/model"
|
|
"github.com/truewhile/MeBox/internal/repository"
|
|
)
|
|
|
|
// 回归:挂载的远程 Emby 剧集里,只看了几秒就退出的那一集必须仍是 NextUp 的结果。
|
|
// 客户端(Yamby 等)剧集详情页的「继续播放」直接取 NextUp 第一条,跳集会播错集。
|
|
func TestMountedRemoteNextUpKeepsPartiallyWatchedEpisode(t *testing.T) {
|
|
episode := func(id string, index int) map[string]any {
|
|
return map[string]any{
|
|
"Id": id,
|
|
"Name": "第" + strconv.Itoa(index) + "集",
|
|
"Type": "Episode",
|
|
"SeriesId": "series-100",
|
|
"ParentIndexNumber": 1,
|
|
"IndexNumber": index,
|
|
"RunTimeTicks": 14400640000,
|
|
}
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch r.URL.Path {
|
|
case "/emby/Users/uid-1/Items/series-100":
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"Id": "series-100", "Name": "剧一", "Type": "Series"})
|
|
case "/emby/Users/uid-1/Items/ep-2":
|
|
_ = json.NewEncoder(w).Encode(episode("ep-2", 2))
|
|
case "/emby/Users/uid-1/Items":
|
|
q := r.URL.Query()
|
|
if q.Get("IncludeItemTypes") != "Episode" || q.Get("ParentId") != "series-100" {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"TotalRecordCount": 3,
|
|
"Items": []map[string]any{
|
|
episode("ep-1", 1),
|
|
episode("ep-2", 2),
|
|
episode("ep-3", 3),
|
|
},
|
|
})
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
db := newServiceTestDB(t, &model.StrmAccount{}, &model.EmbyMount{}, &model.PlaybackHistory{}, &model.User{})
|
|
repos := repository.New(db)
|
|
|
|
cfg := &config.Config{}
|
|
remote := NewEmbyRemoteService(cfg, zap.NewNop(), repos, NewCryptoService("", zap.NewNop()))
|
|
svc := NewEmbyService(cfg, zap.NewNop(), repos).SetEmbyRemote(remote)
|
|
|
|
rawConfig, _ := json.Marshal(map[string]string{
|
|
"url": server.URL,
|
|
"api_key": "test-api-key",
|
|
"remote_user_id": "uid-1",
|
|
})
|
|
acct := &model.StrmAccount{
|
|
Base: model.Base{ID: "acct-1"},
|
|
Name: "远程 Emby",
|
|
Provider: model.StrmProviderEmbyRemote,
|
|
Config: string(rawConfig),
|
|
Enabled: true,
|
|
}
|
|
if err := repos.StrmAccount.Create(t.Context(), acct); err != nil {
|
|
t.Fatalf("create account: %v", err)
|
|
}
|
|
mount := &model.EmbyMount{
|
|
Base: model.Base{ID: "mount-1"},
|
|
AccountID: acct.ID,
|
|
RemoteViewID: "view-1",
|
|
RemoteViewName: "新番连载",
|
|
CollectionType: "tvshows",
|
|
Enabled: true,
|
|
}
|
|
if err := repos.EmbyMount.Create(t.Context(), mount); err != nil {
|
|
t.Fatalf("create mount: %v", err)
|
|
}
|
|
user := &model.User{
|
|
Base: model.Base{ID: "user-1"},
|
|
Username: "viewer",
|
|
PasswordHash: "x",
|
|
Role: "user",
|
|
Tier: "free",
|
|
IsActive: true,
|
|
}
|
|
if err := repos.User.Create(t.Context(), user); err != nil {
|
|
t.Fatalf("create user: %v", err)
|
|
}
|
|
|
|
// 第 2 集只播了 3.5 秒就退出:有进度、未标记看完。
|
|
if err := repos.DB.Create(&model.PlaybackHistory{
|
|
UserID: user.ID,
|
|
MediaID: EncodeEmbyRemoteID(mount.ID, "ep-2"),
|
|
PositionMs: 3582,
|
|
DurationMs: 1440064,
|
|
WatchedAt: time.Now(),
|
|
Completed: false,
|
|
}).Error; err != nil {
|
|
t.Fatalf("create history: %v", err)
|
|
}
|
|
|
|
envelope, err := svc.NextUp(t.Context(), user.ID, EncodeEmbyRemoteID(mount.ID, "series-100"), 1)
|
|
if err != nil {
|
|
t.Fatalf("NextUp: %v", err)
|
|
}
|
|
items, _ := envelope["Items"].([]map[string]any)
|
|
if len(items) != 1 {
|
|
t.Fatalf("items = %d, want 1 (%#v)", len(items), envelope)
|
|
}
|
|
wantID := EncodeEmbyRemoteID(mount.ID, "ep-2")
|
|
if id, _ := items[0]["Id"].(string); id != wantID {
|
|
t.Fatalf("Id = %q, want %q (未看完的那一集不能被跳过)", id, wantID)
|
|
}
|
|
userData, _ := items[0]["UserData"].(map[string]any)
|
|
if ticks, _ := userData["PlaybackPositionTicks"].(int64); ticks != 35820000 {
|
|
t.Fatalf("PlaybackPositionTicks = %#v, want 35820000 (详情页要能续播到 3.5 秒)", userData["PlaybackPositionTicks"])
|
|
}
|
|
}
|
|
|
|
// 远程剧集已看完当前一集时,NextUp 仍要指向下一集。
|
|
func TestMountedRemoteNextUpAfterCompletedEpisode(t *testing.T) {
|
|
episode := func(id string, index int) map[string]any {
|
|
return map[string]any{
|
|
"Id": id,
|
|
"Name": "第" + strconv.Itoa(index) + "集",
|
|
"Type": "Episode",
|
|
"SeriesId": "series-100",
|
|
"ParentIndexNumber": 1,
|
|
"IndexNumber": index,
|
|
"RunTimeTicks": 14400640000,
|
|
}
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch r.URL.Path {
|
|
case "/emby/Users/uid-1/Items/series-100":
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"Id": "series-100", "Name": "剧一", "Type": "Series"})
|
|
case "/emby/Users/uid-1/Items/ep-3":
|
|
_ = json.NewEncoder(w).Encode(episode("ep-3", 3))
|
|
case "/emby/Users/uid-1/Items":
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"TotalRecordCount": 3,
|
|
"Items": []map[string]any{
|
|
episode("ep-1", 1),
|
|
episode("ep-2", 2),
|
|
episode("ep-3", 3),
|
|
},
|
|
})
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
db := newServiceTestDB(t, &model.StrmAccount{}, &model.EmbyMount{}, &model.PlaybackHistory{}, &model.User{})
|
|
repos := repository.New(db)
|
|
|
|
cfg := &config.Config{}
|
|
remote := NewEmbyRemoteService(cfg, zap.NewNop(), repos, NewCryptoService("", zap.NewNop()))
|
|
svc := NewEmbyService(cfg, zap.NewNop(), repos).SetEmbyRemote(remote)
|
|
|
|
rawConfig, _ := json.Marshal(map[string]string{
|
|
"url": server.URL,
|
|
"api_key": "test-api-key",
|
|
"remote_user_id": "uid-1",
|
|
})
|
|
acct := &model.StrmAccount{
|
|
Base: model.Base{ID: "acct-1"},
|
|
Name: "远程 Emby",
|
|
Provider: model.StrmProviderEmbyRemote,
|
|
Config: string(rawConfig),
|
|
Enabled: true,
|
|
}
|
|
if err := repos.StrmAccount.Create(t.Context(), acct); err != nil {
|
|
t.Fatalf("create account: %v", err)
|
|
}
|
|
mount := &model.EmbyMount{
|
|
Base: model.Base{ID: "mount-1"},
|
|
AccountID: acct.ID,
|
|
RemoteViewID: "view-1",
|
|
RemoteViewName: "新番连载",
|
|
CollectionType: "tvshows",
|
|
Enabled: true,
|
|
}
|
|
if err := repos.EmbyMount.Create(t.Context(), mount); err != nil {
|
|
t.Fatalf("create mount: %v", err)
|
|
}
|
|
user := &model.User{
|
|
Base: model.Base{ID: "user-1"},
|
|
Username: "viewer",
|
|
PasswordHash: "x",
|
|
Role: "user",
|
|
Tier: "free",
|
|
IsActive: true,
|
|
}
|
|
if err := repos.User.Create(t.Context(), user); err != nil {
|
|
t.Fatalf("create user: %v", err)
|
|
}
|
|
|
|
// 第 2 集已整集看完。
|
|
if err := repos.DB.Create(&model.PlaybackHistory{
|
|
UserID: user.ID,
|
|
MediaID: EncodeEmbyRemoteID(mount.ID, "ep-2"),
|
|
PositionMs: 1440064,
|
|
DurationMs: 1440064,
|
|
WatchedAt: time.Now(),
|
|
Completed: true,
|
|
}).Error; err != nil {
|
|
t.Fatalf("create history: %v", err)
|
|
}
|
|
|
|
envelope, err := svc.NextUp(context.Background(), user.ID, EncodeEmbyRemoteID(mount.ID, "series-100"), 1)
|
|
if err != nil {
|
|
t.Fatalf("NextUp: %v", err)
|
|
}
|
|
items, _ := envelope["Items"].([]map[string]any)
|
|
if len(items) != 1 {
|
|
t.Fatalf("items = %d, want 1 (%#v)", len(items), envelope)
|
|
}
|
|
if id, _ := items[0]["Id"].(string); id != EncodeEmbyRemoteID(mount.ID, "ep-3") {
|
|
t.Fatalf("Id = %q, want ep-3", id)
|
|
}
|
|
}
|