mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-07 22:06:38 +08:00
优化
This commit is contained in:
@@ -151,7 +151,7 @@ func TestSourceLoginEndToEnd(t *testing.T) {
|
||||
sourceID := prepareLoginSource(t, svc, loginTestSourceJSON(t, srv.URL))
|
||||
|
||||
// ── 登录前:未鉴权,站点返回未登录页 → 搜不到书 ──
|
||||
if books, _, err := svc.Search(ctx, "会员", nil); err != nil {
|
||||
if books, _, err := svc.Search(ctx, "会员", nil, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(books) != 0 {
|
||||
t.Fatalf("未登录时不应搜到结果: %+v", books)
|
||||
@@ -188,7 +188,7 @@ func TestSourceLoginEndToEnd(t *testing.T) {
|
||||
}
|
||||
|
||||
// ── 登录后搜索:应携带 Cookie 并成功 ──
|
||||
books, skipped, err := svc.Search(ctx, "会员", nil)
|
||||
books, skipped, err := svc.Search(ctx, "会员", nil, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func TestSourceLoginEndToEnd(t *testing.T) {
|
||||
if info.LoggedIn || len(info.Cookies) != 0 {
|
||||
t.Fatalf("登出后不应残留登录态: %+v", info)
|
||||
}
|
||||
if books, _, err := svc.Search(ctx, "会员", nil); err != nil {
|
||||
if books, _, err := svc.Search(ctx, "会员", nil, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(books) != 0 {
|
||||
t.Fatalf("登出后不应还能搜到结果: %+v", books)
|
||||
@@ -367,7 +367,7 @@ func TestEnabledCookieJarGating(t *testing.T) {
|
||||
}`
|
||||
sourceID := prepareLoginSource(t, svc, srcJSON)
|
||||
|
||||
if _, _, err := svc.Search(ctx, "任意", nil); err != nil {
|
||||
if _, _, err := svc.Search(ctx, "任意", nil, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// 自动捕获被关闭:不应出现 auto=from-response
|
||||
@@ -389,7 +389,7 @@ func TestEnabledCookieJarGating(t *testing.T) {
|
||||
if _, err := svc.ImportSources(ctx, srcJSONOn); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, _, err := svc.Search(ctx, "任意", nil); err != nil {
|
||||
if _, _, err := svc.Search(ctx, "任意", nil, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := svc.newSourceState(ctx, srv2.URL).GetCookie(srv2.URL); !strings.Contains(got, "auto=from-response") {
|
||||
|
||||
@@ -766,7 +766,14 @@ type searchHit struct {
|
||||
// 空表示「全部书源」——所有已启用书源;非空则只搜其中仍存在、仍启用的书源。
|
||||
// 范围内一个可搜书源都不剩时退回全部启用(对应 legado 范围失效时的兜底),
|
||||
// 这样删源/停源后不会因为残留的旧选择把搜索变成「什么都搜不到」。
|
||||
func (s *ReaderService) Search(ctx context.Context, key string, sourceIDs []string) ([]SearchBook, []SearchSkipped, error) {
|
||||
//
|
||||
// page 是页码(从 1 开始,对应书源 searchUrl 里的 {{page}}):搜索按页下发,
|
||||
// 前端滚到底再请求下一页并做增量合并。搜索范围内的所有源共用同一个页码;
|
||||
// searchUrl 里没有 {{page}} 的源会重复返回首页结果,由调用方按「书名+作者」去重。
|
||||
func (s *ReaderService) Search(ctx context.Context, key string, sourceIDs []string, page int) ([]SearchBook, []SearchSkipped, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
sources, err := s.repo.ListSources(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -790,7 +797,7 @@ func (s *ReaderService) Search(ctx context.Context, key string, sourceIDs []stri
|
||||
g.Go(func() error {
|
||||
gctxSrc, cancel := context.WithTimeout(gctx, perSourceTimeout)
|
||||
defer cancel()
|
||||
books, err := s.searchInSource(gctxSrc, &src, nil, key, 1)
|
||||
books, err := s.searchInSource(gctxSrc, &src, nil, key, page)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package reader
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 本文件:搜索分页。
|
||||
//
|
||||
// 搜索页只发第一页时,书源 searchUrl 里的 {{page}} 永远是 1,用户滚到底也拿不到
|
||||
// 后面的结果(表现为「搜索无法翻页,只有第一页」)。所以页码必须由调用方传入并
|
||||
// 原样交给 AnalyzeUrl;非法页码(0、负数)按第 1 页处理,避免拼出 page=0 的地址。
|
||||
|
||||
func pagedSearchSourceJSON(serverURL string) string {
|
||||
src := map[string]any{
|
||||
"bookSourceUrl": serverURL,
|
||||
"bookSourceName": "分页源",
|
||||
"searchUrl": serverURL + "/search?q={{key}}&page={{page}}",
|
||||
"ruleSearch": map[string]any{
|
||||
"bookList": "$.data",
|
||||
"name": "$.book_name",
|
||||
"author": "$.author",
|
||||
"bookUrl": "$.book_url",
|
||||
},
|
||||
}
|
||||
out, _ := json.Marshal(src)
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func TestSearchUsesRequestedPage(t *testing.T) {
|
||||
var gotPage string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotPage = r.URL.Query().Get("page")
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
payload := map[string]any{"data": []map[string]string{{
|
||||
"book_name": fmt.Sprintf("第%s页的书", gotPage),
|
||||
"author": "作者",
|
||||
"book_url": "/book/" + gotPage,
|
||||
}}}
|
||||
_ = json.NewEncoder(w).Encode(payload)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
svc, _ := newLoginTestService(t)
|
||||
_ = prepareLoginSource(t, svc, pagedSearchSourceJSON(srv.URL))
|
||||
|
||||
for _, tc := range []struct {
|
||||
in int
|
||||
want string
|
||||
}{
|
||||
{1, "1"},
|
||||
{2, "2"},
|
||||
{5, "5"},
|
||||
{0, "1"}, // 非法页码退回第 1 页
|
||||
{-3, "1"}, // 负数同理
|
||||
} {
|
||||
books, skipped, err := svc.Search(t.Context(), "关键词", nil, tc.in)
|
||||
if err != nil {
|
||||
t.Fatalf("page=%d 搜索失败: %v", tc.in, err)
|
||||
}
|
||||
if len(skipped) > 0 {
|
||||
t.Fatalf("page=%d 书源被跳过: %+v", tc.in, skipped)
|
||||
}
|
||||
if len(books) != 1 {
|
||||
t.Fatalf("page=%d 应搜到 1 条,实际 %d 条", tc.in, len(books))
|
||||
}
|
||||
if gotPage != tc.want {
|
||||
t.Fatalf("page=%d 时服务端收到的 page = %q,期望 %q", tc.in, gotPage, tc.want)
|
||||
}
|
||||
if books[0].Name != fmt.Sprintf("第%s页的书", tc.want) {
|
||||
t.Fatalf("page=%d 拿到了别的页的结果: %+v", tc.in, books[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSearchWithoutPagePlaceholderStillWorks 书源 searchUrl 不带 {{page}} 时
|
||||
// 仍应正常返回首页结果(分页能力是源自己的事,不是搜索的硬要求)。
|
||||
func TestSearchWithoutPagePlaceholderStillWorks(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_, _ = w.Write([]byte(`{"data":[{"book_name":"单页书","author":"作者","book_url":"/book/1"}]}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
svc, _ := newLoginTestService(t)
|
||||
_ = prepareLoginSource(t, svc, scopeEchoSourceJSON(t, "单页源", srv.URL))
|
||||
|
||||
books, _, err := svc.Search(t.Context(), "书", nil, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("搜索失败: %v", err)
|
||||
}
|
||||
if len(books) != 1 || books[0].Name != "单页书" {
|
||||
t.Fatalf("不带 {{page}} 的源在第 3 页也应返回结果: %+v", books)
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func TestSearchSendsQueryString(t *testing.T) {
|
||||
svc, _ := newLoginTestService(t)
|
||||
_ = prepareLoginSource(t, svc, queryEchoSource(srv.URL))
|
||||
|
||||
books, skipped, err := svc.Search(t.Context(), "宠魅", nil)
|
||||
books, skipped, err := svc.Search(t.Context(), "宠魅", nil, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("搜索失败: %v", err)
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func TestSearchResponseUsesEmptyArrays(t *testing.T) {
|
||||
svc, _ := newLoginTestService(t)
|
||||
_ = prepareLoginSource(t, svc, queryEchoSource(srv.URL))
|
||||
|
||||
books, skipped, err := svc.Search(t.Context(), "宠魅", nil)
|
||||
books, skipped, err := svc.Search(t.Context(), "宠魅", nil, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("搜索失败: %v", err)
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func TestSearchPreservesExistingPercentEncoding(t *testing.T) {
|
||||
out, _ := json.Marshal(src)
|
||||
_ = prepareLoginSource(t, svc, string(out))
|
||||
|
||||
if _, _, err := svc.Search(t.Context(), "宠魅", nil); err != nil {
|
||||
if _, _, err := svc.Search(t.Context(), "宠魅", nil, 1); err != nil {
|
||||
t.Fatalf("搜索失败: %v", err)
|
||||
}
|
||||
if gotQuery != "宠魅" {
|
||||
|
||||
@@ -75,7 +75,7 @@ func TestSearchRespectsSourceScope(t *testing.T) {
|
||||
}
|
||||
|
||||
// 指定单个源:只应搜到该源的结果
|
||||
books, skipped, err := svc.Search(t.Context(), "书", []string{idByName["源A"]})
|
||||
books, skipped, err := svc.Search(t.Context(), "书", []string{idByName["源A"]}, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func TestSearchRespectsSourceScope(t *testing.T) {
|
||||
}
|
||||
|
||||
// 空范围:默认全部启用书源,两个源都应命中
|
||||
books, _, err = svc.Search(t.Context(), "书", nil)
|
||||
books, _, err = svc.Search(t.Context(), "书", nil, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func TestSearchRespectsSourceScope(t *testing.T) {
|
||||
}
|
||||
|
||||
// 范围里的源已不存在(删源/换设备)时退回全部启用,而不是搜不到
|
||||
books, _, err = svc.Search(t.Context(), "书", []string{"not-a-real-source"})
|
||||
books, _, err = svc.Search(t.Context(), "书", []string{"not-a-real-source"}, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user