Files
legado-ios/Tests/LegadoCoreTests/SampledRequestRegressionTests.swift
T
Aminark 67bc212ffd feat: 支持 EPUB 阅读并改善规则兼容与阅读稳定性
支持 EPUB 2/3 导入、目录、封面和章节插图,保留本地书管理与阅读进度。
修复 JSON 模板、动态替换与请求参数兼容,以及 RSS 翻页和连续滚动定位。
更新编辑器工具栏和回归测试,发布版本为 0.1.12(build 18)。
2026-10-24 00:58:11 +08:00

90 lines
4.7 KiB
Swift

import Foundation
import Testing
@testable import LegadoCore
// The payloads and host below are synthetic and never use network access.
struct SampledRequestRegressionTests {
@Test func equalsSeparatedSourceHeaderReachesSearchTransport() async throws {
var source = source()
source.header = #"{"type"="app"}"#
source.searchUrl = #"/search,{"method":"POST","body":"keyword={{key}}&page={{page-1}}"}"#
let transport = SampledRequestTransport()
let result = await BookSearchService(transport: transport).search(keyword: "斗罗大陆", page: 1, source: source)
#expect(result.diagnostics.isEmpty)
#expect(result.books.count == 1)
let request = try #require(await transport.requests().first)
#expect(request.value(forHTTPHeaderField: "type") == "app")
#expect(request.httpBody.map { String(decoding: $0, as: UTF8.self) } == "keyword=%E6%96%97%E7%BD%97%E5%A4%A7%E9%99%86&page=0")
}
@Test func stringEncodedLenientHeadersReachSearchTransport() async throws {
var source = source()
source.searchUrl = #"/search?word={{key}},{"headers":"{os:'pc'}"}"#
let transport = SampledRequestTransport()
let result = await BookSearchService(transport: transport).search(keyword: "斗罗大陆", page: 1, source: source)
#expect(result.diagnostics.isEmpty)
#expect(result.books.count == 1)
let request = try #require(await transport.requests().first)
#expect(request.value(forHTTPHeaderField: "os") == "pc")
}
@Test func unquotedKeywordInObjectBodyPreservesJSONValueTypes() async throws {
var source = source()
source.searchUrl = #"/search,{"method":"POST","body":{"keyword":{{key}},"page_index":{{page}},"page_size":20,"enabled":true,"cursor":null}}"#
let transport = SampledRequestTransport()
let result = await BookSearchService(transport: transport).search(keyword: "斗罗大陆", page: 1, source: source)
#expect(result.diagnostics.isEmpty)
#expect(result.books.count == 1)
let request = try #require(await transport.requests().first)
let body = try JSONValue.decode(data: #require(request.httpBody))
#expect(body["keyword"] == .string("斗罗大陆"))
#expect(body["page_index"] == .number(1))
#expect(body["page_size"] == .number(20))
#expect(body["enabled"] == .bool(true))
#expect(body["cursor"] == .null)
#expect(request.value(forHTTPHeaderField: "Content-Type")?.hasPrefix("application/json") == true)
}
@Test func lenientSeparatorsDoNotRewriteQuotedCookieOrURLValues() throws {
let value = try LegadoURLBuilder.decodeLenientJSON(#"{type=>app, 'Cookie':'token=a=b; mode=pc', referer:'https://fixture.invalid/?a=b', retry:2, enabled:false, empty:null}"#)
#expect(value["type"] == .string("app"))
#expect(value["Cookie"] == .string("token=a=b; mode=pc"))
#expect(value["referer"] == .string("https://fixture.invalid/?a=b"))
#expect(value["retry"] == .number(2))
#expect(value["enabled"] == .bool(false))
#expect(value["empty"] == .null)
}
@Test func unquotedWebViewFlagUsedByAudioSourceParses() throws {
let request = try LegadoURLBuilder().prepare("https://request.fixture.invalid/audio,{webView:“true”}")
#expect(request.useWebView)
}
@Test func lenientValuesKeepCommentsAndPutRuleExpressionsWorking() throws {
let options = try LegadoURLBuilder.decodeLenientJSON("{retry: 2 /* comment */, enabled: FALSE, word: 小说}")
#expect(options["retry"] == .number(2))
#expect(options["enabled"] == .bool(false))
#expect(options["word"] == .string("小说"))
let put = try LegadoURLBuilder.decodeLenientJSON("{id:$.books[0].id, name:name}", quoteUnquotedValues: true)
#expect(put["id"] == .string("$.books[0].id"))
#expect(put["name"] == .string("name"))
#expect(throws: (any Error).self) { try LegadoURLBuilder.decodeLenientJSON("{broken:}") }
}
private func source() -> BookSource {
var source = BookSource(bookSourceUrl: "https://request.fixture.invalid", bookSourceName: "请求兼容测试")
source.ruleSearch = .init(bookList: "$.books", name: "$.name", bookUrl: "$.url")
return source
}
}
private actor SampledRequestTransport: HTTPTransport {
private var values: [URLRequest] = []
func data(for request: URLRequest) async throws -> HTTPResponse {
values.append(request)
return .init(data: Data(#"{"books":[{"name":"测试书","url":"/book/42"}]}"#.utf8),
statusCode: 200, headers: [:], finalURL: try #require(request.url))
}
func requests() -> [URLRequest] { values }
}