Files
MeBox/internal/service/reader/proxy_test.go
truewhile fa41936095 feat(reader): P3+P4——音频源与漫画/图片源全链路支持
后端:
- /api/reader/media 签名媒体代理:HMAC-SHA256(jwtSecret, bookID|url) 防开放
  中转滥用;免 JWT(audio/img 元素带不了头)但需有效签名+书籍存在
- 携书源级请求头与 Referer(防盗链)、Range 透传(音频拖动进度 206)、
  m3u8 播放列表分片/密钥地址改写为签名代理(hls.js 无感续播)
- 音频/图片地址按最终页面 URL 绝对化,imageStyle 透传;
  GetContentForBook 将 tracks/images 统一改写为代理地址
前端:
- ReaderAudioPanel(仿 ReadAloudDialog):hls.js 播 m3u8 / <audio> 直链,
  上一章/播放暂停/下一章、进度条拖动、倍速 0.75–2x、按秒记忆进度、
  播完自动下一章、点背景呼出菜单
- ReaderComic:上下滚动 / 左右单页双模式(复用翻页/滚动切换)、
  图片懒加载+失败占位、滚动模式左右点击滚屏/中间呼菜单、
  单页模式点击区翻页、菜单进度条按图片序号、按图片序号记忆进度
- 菜单进度条按内容类型适配(文本页/音频章节/漫画图片序号)
2026-09-30 17:12:16 +08:00

44 lines
1.2 KiB
Go

package reader
import (
"net/url"
"testing"
"github.com/truewhile/MeBox/internal/config"
)
// TestProxyURLSignRoundTrip 验证媒体代理签名:往返还原 + 防篡改。
func TestProxyURLSignRoundTrip(t *testing.T) {
s := &ReaderService{cfg: &config.Config{Secrets: config.SecretsConfig{JWTSecret: "test-secret"}}}
const raw = "https://cdn.example.com/audio/ep1.mp3?token=abc"
signed := s.ProxyURL("book-1", raw)
parsed, err := url.Parse(signed)
if err != nil {
t.Fatal(err)
}
q := parsed.Query()
if q.Get("b") != "book-1" {
t.Fatalf("bookID = %q", q.Get("b"))
}
restored, err := s.VerifyProxyURL(q.Get("b"), q.Get("u"), q.Get("s"))
if err != nil {
t.Fatalf("verify failed: %v", err)
}
if restored != raw {
t.Fatalf("restored = %q", restored)
}
// 篡改签名必须被拒绝
if _, err := s.VerifyProxyURL(q.Get("b"), q.Get("u"), "00000000000000000000000000000000"); err == nil {
t.Fatal("expected signature rejection")
}
// 换书签必须被拒绝
if _, err := s.VerifyProxyURL("book-2", q.Get("u"), q.Get("s")); err == nil {
t.Fatal("expected cross-book signature rejection")
}
// 已是代理地址的不再二次包裹
if again := s.ProxyURL("book-1", signed); again != signed {
t.Fatalf("double wrap: %q", again)
}
}