mirror of
https://github.com/Aminark/legado-ios.git
synced 2026-10-20 19:21:22 +08:00
163 lines
7.1 KiB
Swift
163 lines
7.1 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import LegadoCore
|
|
|
|
private struct BookFlowMockTransport: HTTPTransport {
|
|
let pages: [String: String]
|
|
|
|
func data(for request: URLRequest) async throws -> HTTPResponse {
|
|
let url = try #require(request.url)
|
|
let body = try #require(pages[url.absoluteString], "缺少 mock 页面:\(url.absoluteString)")
|
|
return .init(data: Data(body.utf8), statusCode: 200, headers: [:], finalURL: url)
|
|
}
|
|
}
|
|
|
|
struct NetworkBookServiceTests {
|
|
@Test func loadsDetailPaginatedTocAndPaginatedContent() async throws {
|
|
let transport = BookFlowMockTransport(pages: [
|
|
"https://reader.example.test/book/1": """
|
|
<main class="book"><h1>迁移实战</h1><a class="author">作者甲</a>
|
|
<img class="cover" src="/cover.jpg"><p class="intro">一本测试书</p>
|
|
<a class="toc" href="/book/1/toc/1">目录</a></main>
|
|
""",
|
|
"https://reader.example.test/book/1/toc/1": """
|
|
<ul><li class="chapter"><a href="/chapter/1">第一章</a></li></ul>
|
|
<a class="next" href="/book/1/toc/2">下一页</a>
|
|
""",
|
|
"https://reader.example.test/book/1/toc/2": """
|
|
<ul><li class="chapter"><a href="/chapter/2">第二章</a></li></ul>
|
|
""",
|
|
"https://reader.example.test/chapter/1": """
|
|
<h1>第一章 新标题</h1><article><p>第一段</p></article>
|
|
<a class="next" href="/chapter/1?page=2">下一页</a>
|
|
""",
|
|
"https://reader.example.test/chapter/1?page=2": """
|
|
<article><p>第二段</p></article>
|
|
"""
|
|
])
|
|
var source = BookSource(
|
|
bookSourceUrl: "https://reader.example.test",
|
|
bookSourceName: "完整链路源"
|
|
)
|
|
var info = BookInfoRule()
|
|
info.`init` = "@CSS:main.book"
|
|
info.name = "@CSS:h1@text"
|
|
info.author = "@CSS:.author@text"
|
|
info.intro = "@CSS:.intro@text"
|
|
info.coverUrl = "@CSS:img.cover@src"
|
|
info.tocUrl = "@CSS:a.toc@href"
|
|
source.ruleBookInfo = info
|
|
var toc = TocRule()
|
|
toc.chapterList = "@CSS:li.chapter"
|
|
toc.chapterName = "@CSS:a@text"
|
|
toc.chapterUrl = "@CSS:a@href"
|
|
toc.nextTocUrl = "@CSS:a.next@href"
|
|
source.ruleToc = toc
|
|
var content = ContentRule()
|
|
content.content = "@CSS:article p@text"
|
|
content.title = "@CSS:h1@text"
|
|
content.nextContentUrl = "@CSS:a.next@href"
|
|
source.ruleContent = content
|
|
let searchBook = SearchBook(
|
|
bookURL: "https://reader.example.test/book/1",
|
|
origin: source.bookSourceUrl,
|
|
originName: source.bookSourceName,
|
|
type: 0,
|
|
name: "搜索结果名"
|
|
)
|
|
let service = NetworkBookService(transport: transport)
|
|
|
|
let detail = await service.loadDetail(searchBook: searchBook, source: source)
|
|
#expect(detail.book.name == "迁移实战")
|
|
#expect(detail.book.author == "作者甲")
|
|
#expect(detail.book.coverUrl == "https://reader.example.test/cover.jpg")
|
|
#expect(detail.book.tocUrl == "https://reader.example.test/book/1/toc/1")
|
|
#expect(detail.diagnostics.isEmpty)
|
|
|
|
let tocReport = await service.loadChapters(book: detail.book, source: source)
|
|
#expect(tocReport.chapters.map(\.title) == ["第一章", "第二章"])
|
|
#expect(tocReport.chapters.map(\.index) == [0, 1])
|
|
#expect(tocReport.pagesVisited == 2)
|
|
#expect(tocReport.diagnostics.isEmpty)
|
|
|
|
let chapter = try #require(tocReport.chapters.first)
|
|
let contentReport = await service.loadContent(
|
|
book: detail.book,
|
|
chapter: chapter,
|
|
source: source,
|
|
nextChapterURL: tocReport.chapters[1].url
|
|
)
|
|
#expect(contentReport.title == "第一章 新标题")
|
|
#expect(contentReport.content == "第一段\n第二段")
|
|
#expect(contentReport.pagesVisited == 2)
|
|
#expect(contentReport.diagnostics.isEmpty)
|
|
}
|
|
|
|
@Test func returnsDiagnosticsForEmptyContentAndStopsTocLoops() async throws {
|
|
let transport = BookFlowMockTransport(pages: [
|
|
"https://reader.example.test/toc": """
|
|
<li class="chapter"><a href="/chapter/empty">空章</a></li>
|
|
<a class="next" href="/toc">循环</a>
|
|
""",
|
|
"https://reader.example.test/chapter/empty": "<article></article>"
|
|
])
|
|
var source = BookSource(bookSourceUrl: "https://reader.example.test", bookSourceName: "诊断源")
|
|
var toc = TocRule()
|
|
toc.chapterList = "@CSS:li.chapter"
|
|
toc.chapterName = "@CSS:a@text"
|
|
toc.chapterUrl = "@CSS:a@href"
|
|
toc.nextTocUrl = "@CSS:a.next@href"
|
|
source.ruleToc = toc
|
|
var content = ContentRule()
|
|
content.content = "@CSS:article@text"
|
|
source.ruleContent = content
|
|
var book = Book(
|
|
bookUrl: "https://reader.example.test/book/empty",
|
|
name: "空书",
|
|
origin: source.bookSourceUrl
|
|
)
|
|
book.tocUrl = "https://reader.example.test/toc"
|
|
let service = NetworkBookService(transport: transport)
|
|
|
|
let tocReport = await service.loadChapters(book: book, source: source)
|
|
#expect(tocReport.pagesVisited == 1)
|
|
#expect(tocReport.chapters.count == 1)
|
|
let chapter = try #require(tocReport.chapters.first)
|
|
let contentReport = await service.loadContent(book: book, chapter: chapter, source: source)
|
|
|
|
#expect(contentReport.content.isEmpty)
|
|
#expect(contentReport.diagnostics.contains { $0.code == "content.empty" })
|
|
}
|
|
|
|
@Test func carriesBookVariablesIntoJavaScriptChapterRules() async throws {
|
|
let transport = BookFlowMockTransport(pages: [
|
|
"https://audio.example.test/toc": #"{"content":{"content":[{"id":2002,"chapterTitle":"第一集"}]}}"#
|
|
])
|
|
var source = BookSource(bookSourceUrl: "https://audio.example.test", bookSourceName: "变量源")
|
|
var toc = TocRule()
|
|
toc.chapterList = "$.content.content"
|
|
toc.chapterName = "$.chapterTitle"
|
|
toc.chapterUrl = "$.id@js:java.put('chapterId', result); '/audio?bookId=' + java.get('bookId') + '&chapterId=' + result"
|
|
source.ruleToc = toc
|
|
var book = Book(
|
|
bookUrl: "https://audio.example.test/book/1001",
|
|
name: "听书",
|
|
origin: source.bookSourceUrl
|
|
)
|
|
book.tocUrl = "https://audio.example.test/toc"
|
|
book.variable = #"{"bookId":"1001"}"#
|
|
|
|
let report = await NetworkBookService(transport: transport).loadChapters(book: book, source: source)
|
|
|
|
let chapter = try #require(report.chapters.first)
|
|
#expect(chapter.url == "https://audio.example.test/audio?bookId=1001&chapterId=2002")
|
|
let variables = try JSONDecoder().decode(
|
|
[String: String].self,
|
|
from: Data(try #require(chapter.variable).utf8)
|
|
)
|
|
#expect(variables["bookId"] == "1001")
|
|
#expect(variables["chapterId"] == "2002")
|
|
#expect(report.diagnostics.isEmpty)
|
|
}
|
|
}
|