586 lines
18 KiB
Go
586 lines
18 KiB
Go
package hd4k
|
||
|
||
// 协议层测试
|
||
//
|
||
// ⚠️ 这里的夹具是**真实抓下来的页面**(testdata/gallery-p{1,2,3}.html,
|
||
// 2026-10-11 从 migmg.ssuu.uk 实抓),不是手写的 mock。
|
||
//
|
||
// 这么做的原因:站点哪天改版,正则失效,靠手写 mock 是发现不了的 ——
|
||
// mock 只验证「代码符合我写 mock 时的想象」。真实样本能真正当回归基线:
|
||
// 解析结果对不上这三个文件里的确切数字,就说明站点变了或代码退化了。
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync"
|
||
"testing"
|
||
)
|
||
|
||
func loadFixture(t *testing.T, name string) string {
|
||
t.Helper()
|
||
data, err := os.ReadFile(filepath.Join("testdata", name))
|
||
if err != nil {
|
||
t.Fatalf("读夹具 %s 失败: %v", name, err)
|
||
}
|
||
return string(data)
|
||
}
|
||
|
||
// ---------------------------------------------------------------- 真实样本回归
|
||
|
||
func TestParseRealGalleryPages(t *testing.T) {
|
||
// 实测基线:53 张图,分 3 页,每页 20/20/13
|
||
cases := []struct {
|
||
file string
|
||
wantImgs int
|
||
wantPages int
|
||
firstFile string
|
||
lastFile string
|
||
}{
|
||
{"gallery-p1.html", 20, 3, "tbox-vol52-4khd.com-001.webp", "tbox-vol52-4khd.com-020.webp"},
|
||
{"gallery-p2.html", 20, 3, "tbox-vol52-4khd.com-021.webp", "tbox-vol52-4khd.com-040.webp"},
|
||
{"gallery-p3.html", 13, 3, "tbox-vol52-4khd.com-041.webp", "tbox-vol52-4khd.com-053.webp"},
|
||
}
|
||
|
||
for _, c := range cases {
|
||
t.Run(c.file, func(t *testing.T) {
|
||
p := ParseGalleryPageHTML(loadFixture(t, c.file))
|
||
|
||
if len(p.Images) != c.wantImgs {
|
||
t.Errorf("图片数 = %d, 想要 %d", len(p.Images), c.wantImgs)
|
||
}
|
||
if p.TotalPages != c.wantPages {
|
||
t.Errorf("总页数 = %d, 想要 %d", p.TotalPages, c.wantPages)
|
||
}
|
||
if len(p.Images) == 0 {
|
||
return
|
||
}
|
||
if got := p.Images[0].Filename; got != c.firstFile {
|
||
t.Errorf("首图文件名 = %q, 想要 %q", got, c.firstFile)
|
||
}
|
||
if got := p.Images[len(p.Images)-1].Filename; got != c.lastFile {
|
||
t.Errorf("尾图文件名 = %q, 想要 %q", got, c.lastFile)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// 推荐位图片(4KHD-beautifulGirls / ?resize= / w1090-h1500-p-k-no-rw)
|
||
// 三页各出现 13 次 —— 如果被当成正文图混进去,数量会明显超标。
|
||
func TestRecommendImagesExcluded(t *testing.T) {
|
||
for _, f := range []string{"gallery-p1.html", "gallery-p2.html", "gallery-p3.html"} {
|
||
p := ParseGalleryPageHTML(loadFixture(t, f))
|
||
for _, im := range p.Images {
|
||
if isRecommendImage(im.URL) {
|
||
t.Errorf("%s: 推荐位图片混进来了: %s", f, im.URL)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 图片 URL 必须是**源站直链**,不能是 Photon 代理。
|
||
//
|
||
// 2026-10-11 实测:批量下载走 i0.wp.com 会打爆 WordPress 免费 CDN 的额度,
|
||
// 被封后连裸请求都返回 429("Access has been disabled due to excessive
|
||
// usage"),同一时刻源站 20 并发却零失败。所以解析阶段就转成源站。
|
||
//
|
||
// 注意源站路径里的 w1300-rw 段必须保留 —— 那是原站自己的缩略图目录,
|
||
// 不是 Photon 的参数;Photon 的 ?w= 查询串则要去掉(源站不认)。
|
||
func TestImageURLOriginNotPhoton(t *testing.T) {
|
||
for _, f := range []string{"gallery-p1.html", "gallery-p2.html", "gallery-p3.html"} {
|
||
p := ParseGalleryPageHTML(loadFixture(t, f))
|
||
if len(p.Images) == 0 {
|
||
t.Fatalf("%s: 一张图都没解析到", f)
|
||
}
|
||
for _, im := range p.Images {
|
||
if IsPhoton(im.URL) {
|
||
t.Errorf("%s: 还是 Photon 代理,会被 429 封: %s", f, im.URL)
|
||
}
|
||
if !strings.Contains(im.URL, "/pic.4khd.com/") {
|
||
t.Errorf("%s: 不是源站 pic.4khd.com: %s", f, im.URL)
|
||
}
|
||
if !strings.Contains(im.URL, "w1300-rw/") {
|
||
t.Errorf("%s: 丢了 w1300-rw 缩略图目录: %s", f, im.URL)
|
||
}
|
||
if strings.Contains(im.URL, "?") {
|
||
t.Errorf("%s: 不该带查询串(源站不认 Photon 的 resize 参数): %s", f, im.URL)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// OriginURL 转换本身的行为
|
||
func TestOriginURL(t *testing.T) {
|
||
cases := []struct{ in, want string }{
|
||
// Photon 代理 → 源站,查询串去掉
|
||
{
|
||
"https://i0.wp.com/pic.4khd.com/-a/b/c/w1300-rw/x.webp?w=1300",
|
||
"https://pic.4khd.com/-a/b/c/w1300-rw/x.webp",
|
||
},
|
||
{
|
||
"https://i1.wp.com/pic.4khd.com/p/q?resize=1,300",
|
||
"https://pic.4khd.com/p/q",
|
||
},
|
||
// 非 Photon:原样返回,不能乱改
|
||
{
|
||
"https://pic.4khd.com/-a/b/c/w1300-rw/x.webp?w=1300",
|
||
"https://pic.4khd.com/-a/b/c/w1300-rw/x.webp?w=1300",
|
||
},
|
||
{
|
||
"https://migmg.ssuu.uk/content/09/x.html",
|
||
"https://migmg.ssuu.uk/content/09/x.html",
|
||
},
|
||
// 路径里恰好含 wp.com 但不是代理
|
||
{
|
||
"https://pic.4khd.com/i2.wp.com/keep/me.webp",
|
||
"https://pic.4khd.com/i2.wp.com/keep/me.webp",
|
||
},
|
||
}
|
||
for _, c := range cases {
|
||
if got := OriginURL(c.in); got != c.want {
|
||
t.Errorf("OriginURL(%q)\n = %q\n 想要 %q", c.in, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestIsPhoton(t *testing.T) {
|
||
if !IsPhoton("https://i0.wp.com/pic.4khd.com/x.webp?w=1300") {
|
||
t.Error("i0.wp.com 应识别为 Photon")
|
||
}
|
||
if IsPhoton("https://pic.4khd.com/i0.wp.com/x.webp") {
|
||
t.Error("路径里的 i0.wp.com 不该算 Photon")
|
||
}
|
||
if IsPhoton("https://migmg.ssuu.uk/") {
|
||
t.Error("普通站点不该算 Photon")
|
||
}
|
||
}
|
||
|
||
// 标题:h3 里是双空格,<title> 里被 WordPress 规范化成单空格。
|
||
// 折叠空白后两者必须一致,否则同一画廊在不同路径上会算出两个目录名。
|
||
func TestTitleExtraction(t *testing.T) {
|
||
p := ParseGalleryPageHTML(loadFixture(t, "gallery-p1.html"))
|
||
|
||
want := "Myu_a(뮤아) SWEETBOX Vol.52[190MB-53photos]"
|
||
if p.Title != want {
|
||
t.Errorf("标题 = %q\n 想要 %q", p.Title, want)
|
||
}
|
||
if strings.Contains(p.Title, " - 4KHD") {
|
||
t.Errorf("标题没去掉站点后缀: %q", p.Title)
|
||
}
|
||
// 下划线/方括号必须是原样 ASCII,不能被转义(实测源文件里就没有反斜杠)
|
||
if !strings.Contains(p.Title, "Myu_a") {
|
||
t.Errorf("标题里的下划线被破坏了: %q", p.Title)
|
||
}
|
||
if !strings.Contains(p.Title, "[190MB-53photos]") {
|
||
t.Errorf("标题里的方括号被破坏了: %q", p.Title)
|
||
}
|
||
}
|
||
|
||
func TestTitleFromTitleTagMatchesH3(t *testing.T) {
|
||
html := loadFixture(t, "gallery-p1.html")
|
||
|
||
viaH3 := extractTitle(html)
|
||
viaTitle := cleanText(reTitle.FindStringSubmatch(html)[1])
|
||
viaTitle = stripTitleSuffix(viaTitle)
|
||
|
||
if viaH3 != viaTitle {
|
||
t.Errorf("h3 与 <title> 提取结果不一致(空白折叠没做对):\n h3 = %q\n title = %q", viaH3, viaTitle)
|
||
}
|
||
}
|
||
|
||
func TestTotalFromTitle(t *testing.T) {
|
||
// 标题里的 [190MB-53photos] 应能取到 53,与 20+20+13 吻合
|
||
if n := TotalFromTitle("Myu_a SWEETBOX Vol.52[190MB-53photos]"); n != 53 {
|
||
t.Errorf("TotalFromTitle = %d, 想要 53", n)
|
||
}
|
||
}
|
||
|
||
// 分页链:p1/p2 有 next,p3 没有
|
||
func TestNextLink(t *testing.T) {
|
||
p1 := ParseGalleryPageHTML(loadFixture(t, "gallery-p1.html"))
|
||
if !strings.HasSuffix(p1.Next, "/2") {
|
||
t.Errorf("p1 的 next 应指向 /2, 实际 %q", p1.Next)
|
||
}
|
||
p2 := ParseGalleryPageHTML(loadFixture(t, "gallery-p2.html"))
|
||
if !strings.HasSuffix(p2.Next, "/3") {
|
||
t.Errorf("p2 的 next 应指向 /3, 实际 %q", p2.Next)
|
||
}
|
||
p3 := ParseGalleryPageHTML(loadFixture(t, "gallery-p3.html"))
|
||
if p3.Next != "" {
|
||
t.Errorf("p3 是末页,不该有 next, 实际 %q", p3.Next)
|
||
}
|
||
}
|
||
|
||
// 三页合起来应正好 53 张,无重复
|
||
func TestNoDuplicateAcrossPages(t *testing.T) {
|
||
seen := map[string]bool{}
|
||
total := 0
|
||
for _, f := range []string{"gallery-p1.html", "gallery-p2.html", "gallery-p3.html"} {
|
||
p := ParseGalleryPageHTML(loadFixture(t, f))
|
||
for _, im := range p.Images {
|
||
if seen[im.URL] {
|
||
t.Errorf("%s 里出现重复图片: %s", f, im.URL)
|
||
}
|
||
seen[im.URL] = true
|
||
total++
|
||
}
|
||
}
|
||
if total != 53 {
|
||
t.Errorf("三页合计 %d 张, 想要 53", total)
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------- URL 解析
|
||
|
||
func TestStripPageSuffix(t *testing.T) {
|
||
cases := []struct{ in, want string }{
|
||
{"/content/09/x.html", "/content/09/x.html"},
|
||
{"/content/09/x.html/", "/content/09/x.html"},
|
||
{"/content/09/x.html/2", "/content/09/x.html"},
|
||
{"/content/09/x.html/12", "/content/09/x.html"},
|
||
{"/content/09/x.html/2/", "/content/09/x.html"},
|
||
// 非数字段不是分页,原样保留(宁可后续报「不是画廊链接」,
|
||
// 也不要盲目砍掉路径的一部分)
|
||
{"/content/09/x.html/abc", "/content/09/x.html/abc"},
|
||
}
|
||
for _, c := range cases {
|
||
if got := StripPageSuffix(c.in); got != c.want {
|
||
t.Errorf("StripPageSuffix(%q) = %q, 想要 %q", c.in, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestParseGalleryURL(t *testing.T) {
|
||
sites := DefaultSites()
|
||
base := "https://migmg.ssuu.uk"
|
||
|
||
cases := []struct {
|
||
in string
|
||
wantID string
|
||
wantPath string
|
||
wantSlug string
|
||
}{
|
||
{
|
||
in: base + "/content/09/myua-sweetbox-vol52.html",
|
||
wantID: "migmg.ssuu.uk/content/09/myua-sweetbox-vol52.html",
|
||
wantPath: "/content/09/myua-sweetbox-vol52.html",
|
||
wantSlug: "myua-sweetbox-vol52.html",
|
||
},
|
||
{
|
||
// 裸 host
|
||
in: "migmg.ssuu.uk/content/09/myua-sweetbox-vol52.html",
|
||
wantID: "migmg.ssuu.uk/content/09/myua-sweetbox-vol52.html",
|
||
wantPath: "/content/09/myua-sweetbox-vol52.html",
|
||
wantSlug: "myua-sweetbox-vol52.html",
|
||
},
|
||
{
|
||
// 第 2 页应归一到第 1 页(否则同一画廊会变成两个任务)
|
||
in: base + "/content/09/myua-sweetbox-vol52.html/2",
|
||
wantID: "migmg.ssuu.uk/content/09/myua-sweetbox-vol52.html",
|
||
wantPath: "/content/09/myua-sweetbox-vol52.html",
|
||
wantSlug: "myua-sweetbox-vol52.html",
|
||
},
|
||
{
|
||
// 尾斜杠
|
||
in: base + "/content/09/myua-sweetbox-vol52.html/",
|
||
wantID: "migmg.ssuu.uk/content/09/myua-sweetbox-vol52.html",
|
||
wantPath: "/content/09/myua-sweetbox-vol52.html",
|
||
wantSlug: "myua-sweetbox-vol52.html",
|
||
},
|
||
}
|
||
|
||
for _, c := range cases {
|
||
ref, err := ParseGalleryURL(c.in, sites)
|
||
if err != nil {
|
||
t.Errorf("ParseGalleryURL(%q) 报错: %v", c.in, err)
|
||
continue
|
||
}
|
||
if ref.ID != c.wantID {
|
||
t.Errorf("ParseGalleryURL(%q).ID = %q, 想要 %q", c.in, ref.ID, c.wantID)
|
||
}
|
||
if ref.Path != c.wantPath {
|
||
t.Errorf("ParseGalleryURL(%q).Path = %q, 想要 %q", c.in, ref.Path, c.wantPath)
|
||
}
|
||
if ref.Slug != c.wantSlug {
|
||
t.Errorf("ParseGalleryURL(%q).Slug = %q, 想要 %q", c.in, ref.Slug, c.wantSlug)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 不同域名下的同一 slug 必须是两个不同的任务
|
||
func TestSameSlugDifferentHost(t *testing.T) {
|
||
// 两个**互不相关**的站点(都不在默认别名表里)
|
||
sites := []Site{
|
||
{Host: "aaa.example", Enabled: true},
|
||
{Host: "bbb.example", Enabled: true},
|
||
}
|
||
a, err := ParseGalleryURL("https://aaa.example/content/09/x.html", sites)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
b, err := ParseGalleryURL("https://bbb.example/content/09/x.html", sites)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if a.ID == b.ID {
|
||
t.Errorf("不同站点下的同 slug 被当成同一个画廊了: %s", a.ID)
|
||
}
|
||
}
|
||
|
||
// 别名域名必须归一到主域名。
|
||
//
|
||
// 这是实测逼出来的:migmg.ssuu.uk 首页里 90 个 /content/ 链接全都指向
|
||
// www.4khd.com,而那个域名已经 404。不归一的话,同一个画廊会因为
|
||
// 「从哪个域名复制的链接」变成两个不同任务,后端还会去请求死域名。
|
||
func TestAliasNormalizesToPrimaryHost(t *testing.T) {
|
||
sites := DefaultSites()
|
||
path := "/content/09/myua-sweetbox-vol52.html"
|
||
|
||
want := "migmg.ssuu.uk" + path
|
||
for _, in := range []string{
|
||
"https://migmg.ssuu.uk" + path,
|
||
"https://www.4khd.com" + path,
|
||
"https://4khd.com" + path,
|
||
"https://m.4khd.com" + path,
|
||
} {
|
||
ref, err := ParseGalleryURL(in, sites)
|
||
if err != nil {
|
||
t.Errorf("ParseGalleryURL(%q) 报错: %v", in, err)
|
||
continue
|
||
}
|
||
if ref.ID != want {
|
||
t.Errorf("ParseGalleryURL(%q).ID = %q, 想要 %q", in, ref.ID, want)
|
||
}
|
||
if ref.Host != "migmg.ssuu.uk" {
|
||
t.Errorf("ParseGalleryURL(%q).Host = %q, 想要归一到 migmg.ssuu.uk", in, ref.Host)
|
||
}
|
||
if !strings.Contains(ref.URL, "https://migmg.ssuu.uk/") {
|
||
t.Errorf("ParseGalleryURL(%q).URL = %q, 应指向主域名", in, ref.URL)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestAllHostsIncludesAliases(t *testing.T) {
|
||
hosts := AllHosts(DefaultSites())
|
||
for _, want := range []string{"migmg.ssuu.uk", "www.4khd.com", "4khd.com", "m.4khd.com"} {
|
||
found := false
|
||
for _, h := range hosts {
|
||
if h == want {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
if !found {
|
||
t.Errorf("AllHosts 缺了 %q: %v", want, hosts)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestParseGalleryURLRejectsNonGallery(t *testing.T) {
|
||
sites := DefaultSites()
|
||
bad := []string{
|
||
"",
|
||
"https://migmg.ssuu.uk/",
|
||
"https://migmg.ssuu.uk/pages/popular",
|
||
"https://example.com",
|
||
"随便什么",
|
||
}
|
||
for _, s := range bad {
|
||
if ref, err := ParseGalleryURL(s, sites); err == nil {
|
||
t.Errorf("ParseGalleryURL(%q) 应该报错, 却解析成了 %s", s, ref.ID)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestParseGalleryInput(t *testing.T) {
|
||
sites := DefaultSites()
|
||
text := `
|
||
# 注释行
|
||
https://migmg.ssuu.uk/content/09/a.html
|
||
https://migmg.ssuu.uk/content/09/b.html
|
||
https://migmg.ssuu.uk/content/09/a.html
|
||
// 另一个注释
|
||
|
||
https://migmg.ssuu.uk/content/09/c.html
|
||
`
|
||
refs := ParseGalleryInput(text, sites)
|
||
if len(refs) != 3 {
|
||
t.Fatalf("解析出 %d 个画廊, 想要 3(应去重、忽略注释与空行)", len(refs))
|
||
}
|
||
}
|
||
|
||
func TestNormalizeHost(t *testing.T) {
|
||
cases := []struct{ in, want string }{
|
||
{"https://migmg.ssuu.uk/", "migmg.ssuu.uk"},
|
||
{"http://MIGMG.SSUU.UK", "migmg.ssuu.uk"},
|
||
{"migmg.ssuu.uk", "migmg.ssuu.uk"},
|
||
{"migmg.ssuu.uk/content/09/x.html", "migmg.ssuu.uk"},
|
||
// 默认端口砍掉,非默认端口保留(本地 mock / 自建站要用)
|
||
{"https://migmg.ssuu.uk:443", "migmg.ssuu.uk"},
|
||
{"http://migmg.ssuu.uk:80", "migmg.ssuu.uk"},
|
||
{"https://migmg.ssuu.uk:8443", "migmg.ssuu.uk:8443"},
|
||
{"http://127.0.0.1:9000", "127.0.0.1:9000"},
|
||
}
|
||
for _, c := range cases {
|
||
if got := NormalizeHost(c.in); got != c.want {
|
||
t.Errorf("NormalizeHost(%q) = %q, 想要 %q", c.in, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------- 实体 / 文件名
|
||
|
||
func TestDecodeEntities(t *testing.T) {
|
||
cases := []struct{ in, want string }{
|
||
{"–", "–"},
|
||
{"&", "&"},
|
||
{"»", "»"},
|
||
{"&", "&"},
|
||
{"<tag>", "<tag>"},
|
||
{"A", "A"},
|
||
{"Myu_a [190MB]", "Myu_a [190MB]"}, // 下划线/方括号原样不动
|
||
}
|
||
for _, c := range cases {
|
||
if got := DecodeEntities(c.in); got != c.want {
|
||
t.Errorf("DecodeEntities(%q) = %q, 想要 %q", c.in, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestFilenameFromURL(t *testing.T) {
|
||
cases := []struct{ in, want string }{
|
||
{"https://i0.wp.com/pic.4khd.com/a/b/c/d/w1300-rw/x-001.webp?w=1300", "x-001.webp"},
|
||
{"https://i0.wp.com/pic.4khd.com/a/b/c/d/w1300-rw/x-001.webp", "x-001.webp"},
|
||
{"", ""},
|
||
}
|
||
for _, c := range cases {
|
||
if got := filenameFromURL(c.in); got != c.want {
|
||
t.Errorf("filenameFromURL(%q) = %q, 想要 %q", c.in, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestRenameUniqueHandlesDuplicateNames(t *testing.T) {
|
||
imgs := []ImageInfo{
|
||
{URL: "https://x/1.webp", Filename: "a.webp"},
|
||
{URL: "https://x/2.webp", Filename: "a.webp"},
|
||
{URL: "https://x/3.webp", Filename: "a.webp"},
|
||
}
|
||
renameUnique(imgs)
|
||
|
||
if imgs[0].Filename != "a.webp" {
|
||
t.Errorf("第 1 个应保持 a.webp, 实际 %q", imgs[0].Filename)
|
||
}
|
||
if imgs[1].Filename != "a(2).webp" {
|
||
t.Errorf("第 2 个应改名 a(2).webp, 实际 %q", imgs[1].Filename)
|
||
}
|
||
if imgs[2].Filename != "a(3).webp" {
|
||
t.Errorf("第 3 个应改名 a(3).webp, 实际 %q", imgs[2].Filename)
|
||
}
|
||
seen := map[string]bool{}
|
||
for _, im := range imgs {
|
||
if seen[im.Filename] {
|
||
t.Errorf("文件名仍重复: %s", im.Filename)
|
||
}
|
||
seen[im.Filename] = true
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------- 端到端
|
||
|
||
// TestFetchGalleryEndToEnd 用 mock 服务器跑完整链路:
|
||
// 分页抓取 → 去重 → 排序 → 标题。响应体用真实夹具。
|
||
//
|
||
// 夹具里的 rel="next" / .page-links 指向 www.4khd.com(真实站点),
|
||
// 这里统一替换成 @@BASE@@ 占位符,在 handler 内部再替换成 mock 地址 ——
|
||
// 这样既能跑通全链路,又不会误请求真实站点。
|
||
func TestFetchGalleryEndToEnd(t *testing.T) {
|
||
paths := []string{
|
||
"/content/09/myua-sweetbox-vol52.html",
|
||
"/content/09/myua-sweetbox-vol52.html/2",
|
||
"/content/09/myua-sweetbox-vol52.html/3",
|
||
}
|
||
files := []string{"gallery-p1.html", "gallery-p2.html", "gallery-p3.html"}
|
||
|
||
// 路径 -> 已替换占位符的响应体
|
||
loaded := map[string]string{}
|
||
for i := range paths {
|
||
loaded[paths[i]] = strings.ReplaceAll(
|
||
loadFixture(t, files[i]), "https://www.4khd.com", "@@BASE@@")
|
||
}
|
||
|
||
var mu sync.Mutex
|
||
var gotPaths []string
|
||
var srvURL string // handler 内部才读,NewServer 返回后才赋值
|
||
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
mu.Lock()
|
||
gotPaths = append(gotPaths, r.URL.Path)
|
||
mu.Unlock()
|
||
|
||
body, ok := loaded[r.URL.Path]
|
||
if !ok {
|
||
http.NotFound(w, r)
|
||
return
|
||
}
|
||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
fmt.Fprint(w, strings.ReplaceAll(body, "@@BASE@@", srvURL))
|
||
}))
|
||
defer srv.Close()
|
||
srvURL = srv.URL
|
||
|
||
ref, err := ParseGalleryURL(srv.URL+"/content/09/myua-sweetbox-vol52.html", DefaultSites())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
c := NewClient(0, "", "off", DefaultSites())
|
||
g, err := c.FetchGallery(context.Background(), ref, nil)
|
||
if err != nil {
|
||
t.Fatalf("FetchGallery 失败: %v", err)
|
||
}
|
||
defer c.Close()
|
||
|
||
if g.Total != 53 {
|
||
t.Errorf("图片总数 = %d, 想要 53", g.Total)
|
||
}
|
||
if g.TotalPages != 3 {
|
||
t.Errorf("总页数 = %d, 想要 3", g.TotalPages)
|
||
}
|
||
if !strings.Contains(g.Title, "SWEETBOX Vol.52") {
|
||
t.Errorf("标题不对: %q", g.Title)
|
||
}
|
||
for i, im := range g.Images {
|
||
if im.Index != i+1 {
|
||
t.Fatalf("第 %d 张的序号 = %d, 想要 %d", i, im.Index, i+1)
|
||
}
|
||
}
|
||
if g.Images[0].Filename != "tbox-vol52-4khd.com-001.webp" {
|
||
t.Errorf("首图文件名 = %q", g.Images[0].Filename)
|
||
}
|
||
if g.Images[52].Filename != "tbox-vol52-4khd.com-053.webp" {
|
||
t.Errorf("尾图文件名 = %q", g.Images[52].Filename)
|
||
}
|
||
|
||
mu.Lock()
|
||
seenPaths := append([]string(nil), gotPaths...)
|
||
mu.Unlock()
|
||
if len(seenPaths) != 3 {
|
||
t.Errorf("请求了 %d 次, 想要 3 次: %v", len(seenPaths), seenPaths)
|
||
}
|
||
}
|
||
|
||
func TestPageURL(t *testing.T) {
|
||
base := "https://migmg.ssuu.uk/content/09/x.html"
|
||
if got := PageURL(base, 1); got != base {
|
||
t.Errorf("第 1 页应等于原地址, 实际 %q", got)
|
||
}
|
||
if got := PageURL(base, 3); got != base+"/3" {
|
||
t.Errorf("第 3 页 = %q, 想要 %q", got, base+"/3")
|
||
}
|
||
}
|