mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
122 lines
3.9 KiB
Go
122 lines
3.9 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/glebarez/sqlite"
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/truewhile/MeBox/internal/config"
|
|
"github.com/truewhile/MeBox/internal/model"
|
|
"github.com/truewhile/MeBox/internal/repository"
|
|
"github.com/truewhile/MeBox/internal/service"
|
|
)
|
|
|
|
// newEmbyCompatTestRouter 构造一个挂载了完整 Emby 路由表的测试引擎。
|
|
func newEmbyCompatTestRouter(t *testing.T, secret string) *gin.Engine {
|
|
t.Helper()
|
|
gin.SetMode(gin.TestMode)
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("open db: %v", err)
|
|
}
|
|
if err := db.AutoMigrate(model.AllModels()...); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
repos := repository.New(db)
|
|
if err := repos.User.Create(t.Context(), &model.User{
|
|
Base: model.Base{ID: "user-1"},
|
|
Username: "tester",
|
|
PasswordHash: "x",
|
|
Role: "admin",
|
|
Tier: "plus",
|
|
IsActive: true,
|
|
}); err != nil {
|
|
t.Fatalf("create user: %v", err)
|
|
}
|
|
router := gin.New()
|
|
registerEmbyRoutes(router, secret, &service.Container{
|
|
Repo: repos,
|
|
Emby: service.NewEmbyService(&config.Config{}, zap.NewNop(), repos),
|
|
})
|
|
return router
|
|
}
|
|
|
|
func TestEmbyAdditionalPartsReturnsEmptyArray(t *testing.T) {
|
|
const secret = "test-secret"
|
|
router := newEmbyCompatTestRouter(t, secret)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/emby/Videos/msgo-series-abc/AdditionalParts", nil)
|
|
req.Header.Set("X-Emby-Token", signedTestToken(t, secret))
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200 (body=%s)", w.Code, w.Body.String())
|
|
}
|
|
// 必须命中 AdditionalParts 静态路由并返回空数组,而不是被 /Videos/:id/:seg
|
|
// 的 HLS 兜底路由接走返回空 404。
|
|
var payload []any
|
|
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
|
t.Fatalf("decode body %q: %v", w.Body.String(), err)
|
|
}
|
|
if len(payload) != 0 {
|
|
t.Fatalf("expected an empty array, got %v", payload)
|
|
}
|
|
}
|
|
|
|
func TestEmbyItemImagesWithoutTypeReturnsArray(t *testing.T) {
|
|
router := newEmbyCompatTestRouter(t, "test-secret")
|
|
|
|
// 不带 Type 的图片清单接口是公开路由,不要求 token,与带 Type 的
|
|
// 图片字节流一致(客户端缓存 URL 时会丢 token)。
|
|
req := httptest.NewRequest(http.MethodGet, "/emby/Items/unknown-item/Images", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200 (body=%s)", w.Code, w.Body.String())
|
|
}
|
|
if ct := w.Header().Get("Content-Type"); ct == "" || ct[:16] != "application/json" {
|
|
t.Fatalf("content type = %q, want application/json", ct)
|
|
}
|
|
var payload []any
|
|
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
|
t.Fatalf("decode body %q: %v", w.Body.String(), err)
|
|
}
|
|
}
|
|
|
|
func TestEmbyItemImagesLowerCaseRouteIsRegistered(t *testing.T) {
|
|
router := newEmbyCompatTestRouter(t, "test-secret")
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/emby/items/unknown-item/images", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200 (body=%s)", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestEmbyUserImageWithoutAvatarReturnsCacheableNotFound(t *testing.T) {
|
|
router := newEmbyCompatTestRouter(t, "test-secret")
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/emby/Users/user-1/Images/Primary", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// 用户没有头像时 Emby 同样返回 404,但响应必须可缓存,否则客户端会
|
|
// 在每次进入设置页时重复请求(线上曾观察到每分钟一次的重试)。
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("status = %d, want 404 (body=%s)", w.Code, w.Body.String())
|
|
}
|
|
if cc := w.Header().Get("Cache-Control"); cc != "public, max-age=86400" {
|
|
t.Fatalf("Cache-Control = %q, want the cacheable directive", cc)
|
|
}
|
|
}
|