dreamio/Tests/StreamResolverTests.swift

635 lines
25 KiB
Swift

import Foundation
@main
struct StreamResolverTests {
static func main() async {
testClassifierPrefersObservedDirectFile()
testResolverSelectsUnsupportedDirectURLAndHeaders()
testResolverRejectsHLSOnlyResponse()
testRedactorHandlesPercentEncodedPath()
testPlaybackTimeFormatting()
testSubtitleCandidateParsing()
testOpenSubtitlesV3CandidateParsing()
testOpenSubtitlesNestedAttributesFilesParsing()
testOpenSubtitlesManifestIDsAreNotResolvedAsSubtitles()
testOpenSubtitlesArtworkAndAddonEndpointsAreIgnored()
testStremioSubtitleDownloadURLParsing()
testOpenSubtitlesV3DownloadResponseResolution()
testOpenSubtitlesNestedDownloadResponseResolution()
await testSubtitleResolverDownloadJSONReturningLink()
await testSubtitleResolverRedirectToDirectSubtitle()
await testSubtitleResolverRejectsNonSubtitleAPIResponse()
testSubtitleCandidateDeduplicationPreservesLabels()
testSubtitleCandidateDeduplicationUpgradesLabels()
testSubtitleDisplayNameNormalization()
testSubtitleDisplayNameUsesPreservedNamesForGenericVLCTracks()
testSubtitleOptionMappingIncludesNone()
testHTTPRangeParsing()
testContentRangeFormatting()
testCacheLookupAcrossChunkBoundaries()
testCacheEvictionOutsideByteBudget()
testProxyForwardsUpstreamHeaders()
testProxyPassThroughFallbackStatus()
print("StreamResolverTests passed")
}
private static func testClassifierPrefersObservedDirectFile() {
let body: [String: Any] = [
"url": "https://cdn.example.test/movie.mkv?token=secret",
"resolverUrl": "https://addon.debridio.com/play/example"
]
let candidate = StreamCandidate(messageBody: body)!
let request = StreamClassifier.playbackRequest(from: candidate, userAgent: "DreamioTest/1")!
assertEqual(request.playbackURL.absoluteString, "https://cdn.example.test/movie.mkv?token=secret")
assertEqual(request.headers["Referer"], "https://web.stremio.com/")
assertEqual(request.headers["User-Agent"], "DreamioTest/1")
}
private static func testHTTPRangeParsing() {
assertEqual(HTTPRange.parse("bytes=10-20"), HTTPRange(start: 10, end: 20))
assertEqual(HTTPRange.parse("bytes=10-"), HTTPRange(start: 10, end: nil))
assert(HTTPRange.parse("items=10-20") == nil, "Expected non-byte range to be rejected")
assert(HTTPRange.parse("bytes=-20") == nil, "Expected suffix ranges to be rejected for v1")
assert(HTTPRange.parse("bytes=20-10") == nil, "Expected inverted ranges to be rejected")
assert(HTTPRange.parse("bytes=1-2,3-4") == nil, "Expected multipart ranges to be rejected")
}
private static func testContentRangeFormatting() {
assertEqual(HTTPRange.contentRange(start: 10, end: 20, totalLength: 100), "bytes 10-20/100")
assertEqual(HTTPRange.contentRange(start: 10, end: 20, totalLength: nil), "bytes 10-20/*")
}
private static func testCacheLookupAcrossChunkBoundaries() {
let store = CachedRangeStore(sessionID: "test-\(UUID().uuidString)", byteBudget: 1024)
defer { store.removeAll() }
store.store(data: Data("abc".utf8), start: 0)
store.store(data: Data("def".utf8), start: 3)
let lookup = store.lookup(range: HTTPRange(start: 0, end: 5), maximumLength: 6)
assertEqual(String(data: lookup?.data ?? Data(), encoding: .utf8), "abcdef")
assertEqual(lookup?.isComplete, true)
}
private static func testCacheEvictionOutsideByteBudget() {
let store = CachedRangeStore(sessionID: "test-\(UUID().uuidString)", byteBudget: 6)
defer { store.removeAll() }
store.store(data: Data("abcdef".utf8), start: 0)
store.store(data: Data("ghijkl".utf8), start: 6)
let oldLookup = store.lookup(range: HTTPRange(start: 0, end: 5), maximumLength: 6)
let newLookup = store.lookup(range: HTTPRange(start: 6, end: 11), maximumLength: 6)
assert(oldLookup == nil, "Expected old chunk to be evicted outside the byte budget")
assertEqual(String(data: newLookup?.data ?? Data(), encoding: .utf8), "ghijkl")
}
private static func testProxyForwardsUpstreamHeaders() {
let proxy = NativeStreamCacheProxy(session: NativeStreamCacheProxy.Session(request: proxyTestRequest()))
let upstreamRequest = proxy.upstreamRequest(for: HTTPRange(start: 12, end: 34))
assertEqual(upstreamRequest.value(forHTTPHeaderField: "Range"), "bytes=12-34")
assertEqual(upstreamRequest.value(forHTTPHeaderField: "Referer"), "https://resolver.example.test/")
assertEqual(upstreamRequest.value(forHTTPHeaderField: "User-Agent"), "DreamioTest/1")
assertEqual(upstreamRequest.value(forHTTPHeaderField: "Authorization"), "Bearer secret")
}
private static func testProxyPassThroughFallbackStatus() {
assertEqual(NativeStreamCacheProxy.responseStatusForUpstreamStatus(206), 206)
assertEqual(NativeStreamCacheProxy.responseStatusForUpstreamStatus(200), 200)
}
private static func proxyTestRequest() -> NativePlaybackRequest {
NativePlaybackRequest(
playbackURL: URL(string: "https://cdn.example.test/movie.mkv")!,
observedURL: URL(string: "https://cdn.example.test/movie.mkv")!,
resolverURL: URL(string: "https://resolver.example.test/play")!,
pageURL: nil,
userAgent: "DreamioTest/1",
referer: "https://resolver.example.test/",
headers: [
"Referer": "https://resolver.example.test/",
"User-Agent": "DreamioTest/1",
"Authorization": "Bearer secret"
],
classification: StreamClassification(
sourceKind: .directFile,
containerGuess: .mkv,
reason: "test",
shouldIntercept: true,
sanitizedObservedURL: "https://cdn.example.test/movie.mkv",
sanitizedResolverURL: nil
),
subtitleCandidates: []
)
}
private static func testResolverSelectsUnsupportedDirectURLAndHeaders() {
let payload: [String: Any] = [
"streams": [
[
"url": "https://cdn.example.test/trailer.mp4"
],
[
"externalUrl": "https://cdn.example.test/movie.mkv?signature=secret",
"behaviorHints": [
"proxyHeaders": [
"request": [
"Referer": "https://resolver.example.test/",
"User-Agent": "ResolverAgent/1"
]
]
]
]
]
]
let stream = StremioStreamResolver.bestPlayableStream(
in: payload,
fallbackHeaders: ["Referer": "https://web.stremio.com/"]
)!
assertEqual(stream.playbackURL.absoluteString, "https://cdn.example.test/movie.mkv?signature=secret")
assertEqual(stream.headers["Referer"], "https://resolver.example.test/")
assertEqual(stream.headers["User-Agent"], "ResolverAgent/1")
}
private static func testResolverRejectsHLSOnlyResponse() {
let payload: [String: Any] = [
"streams": [
["url": "https://cdn.example.test/live.m3u8"]
]
]
let stream = StremioStreamResolver.bestPlayableStream(
in: payload,
fallbackHeaders: ["Referer": "https://web.stremio.com/"]
)
assert(stream == nil, "Expected HLS-only resolver response to stay out of native playback")
}
private static func testRedactorHandlesPercentEncodedPath() {
let original = "https://cdn.example.test/video/abcdefghijklmnopqrstuvwxyz012345/%E2%9C%93.mp4?token=secret#fragment"
let redacted = URLRedactor.redactedURLString(original)
assertEqual(redacted, "https://cdn.example.test/video/%5Bredacted%5D/%E2%9C%93.mp4")
}
private static func testPlaybackTimeFormatting() {
assertEqual(PlaybackTimeFormatter.label(for: 0), "0:00")
assertEqual(PlaybackTimeFormatter.label(for: 65), "1:05")
assertEqual(PlaybackTimeFormatter.label(for: 3_725), "1:02:05")
}
private static func testSubtitleCandidateParsing() {
let payload: [String: Any] = [
"subtitles": [
[
"lang": "eng",
"url": "https://opensubtitles.example.test/download/subtitle.srt?token=secret"
],
[
"language": "Spanish",
"file": "https://cdn.example.test/movie.es.vtt"
],
"https://cdn.example.test/ignored.txt"
],
"nested": [
"body": "metadata https://cdn.example.test/movie.fr.ass?download=1"
]
]
let candidates = SubtitleCandidateParser.candidates(in: payload)
assertEqual(candidates.count, 3)
assertEqual(candidates[0].language, "eng")
assertEqual(candidates[1].label, "Spanish")
assertEqual(candidates[2].url.absoluteString, "https://cdn.example.test/movie.fr.ass?download=1")
}
private static func testOpenSubtitlesV3CandidateParsing() {
let payload: [String: Any] = [
"subtitles": [
[
"language": "English",
"download": "https://api.opensubtitles.com/api/v1/download/subtitle-file",
"nested": [
[
"file": "https://dl.opensubtitles.org/en/subtitle.vtt?download=1"
]
]
],
[
"lang": "spa",
"url": "https://opensubtitles.example.test/download/episode.srt"
]
],
"body": "alternate https://cdn.example.test/from-string.ass?source=opensubtitles",
"ignored": [
"https://cdn.example.test/poster.jpg",
["file": "https://cdn.example.test/video.mkv"]
]
]
let candidates = SubtitleCandidateParser.candidates(in: payload)
assertEqual(candidates.count, 4)
assertEqual(candidates[0].label, "English")
assertEqual(candidates[0].language, "English")
assertEqual(candidates[1].url.absoluteString, "https://dl.opensubtitles.org/en/subtitle.vtt?download=1")
assertEqual(candidates[1].label, "English")
assertEqual(candidates[1].language, "English")
assertEqual(candidates[2].label, "spa")
assertEqual(candidates[2].language, "spa")
assertEqual(candidates[3].url.absoluteString, "https://cdn.example.test/from-string.ass?source=opensubtitles")
}
private static func testOpenSubtitlesNestedAttributesFilesParsing() {
let payload: [String: Any] = [
"data": [
[
"attributes": [
"language": "English",
"file_name": "episode.en.srt",
"files": [
[
"file_id": 12345,
"file_name": "nested.en.srt"
],
[
"link": "https://dl.opensubtitles.org/en/download/nested.vtt?token=secret",
"language": "eng"
]
]
]
]
]
]
let candidates = SubtitleCandidateParser.candidates(in: payload)
assertEqual(candidates.count, 2)
assertEqual(candidates[0].url.absoluteString, "https://api.opensubtitles.com/api/v1/download/12345")
assertEqual(candidates[0].label, "nested.en.srt")
assertEqual(candidates[0].language, "English")
assertEqual(candidates[1].url.absoluteString, "https://dl.opensubtitles.org/en/download/nested.vtt?token=secret")
assertEqual(candidates[1].label, "eng")
assertEqual(candidates[1].language, "eng")
}
private static func testOpenSubtitlesManifestIDsAreNotResolvedAsSubtitles() {
let payload: [String: Any] = [
"subtitles": [
[
"url": "https://opensubtitles-v3.strem.io/manifest.json_14",
"file_id": 98765,
"lang": "eng"
],
[
"url": "https://opensubtitles-v3.strem.io/manifest.json_15",
"lang": "spa"
],
"https://opensubtitles-v3.strem.io/manifest.json_16"
]
]
let candidates = SubtitleCandidateParser.candidates(in: payload)
assertEqual(candidates.count, 1)
assertEqual(candidates[0].url.absoluteString, "https://api.opensubtitles.com/api/v1/download/98765")
assertEqual(candidates[0].language, "eng")
}
private static func testOpenSubtitlesArtworkAndAddonEndpointsAreIgnored() {
let payload: [String: Any] = [
"subtitles": [
[
"label": "External Subtitle",
"url": "http://www.strem.io/images/addons/opensubtitles-logo.png"
],
[
"label": "External Subtitle",
"url": "https://opensubtitles.strem.io/stremio/v1"
],
[
"label": "English",
"url": "https://opensubtitles.example.test/subtitles/movie.en.srt"
]
],
"body": "metadata https://www.strem.io/images/addons/opensubtitles-logo.png"
]
let candidates = SubtitleCandidateParser.candidates(in: payload)
assertEqual(candidates.count, 1)
assertEqual(candidates[0].url.absoluteString, "https://opensubtitles.example.test/subtitles/movie.en.srt")
assertEqual(candidates[0].label, "English")
}
private static func testStremioSubtitleDownloadURLParsing() {
let payload: [String: Any] = [
"subtitles": [
[
"label": "English",
"lang": "eng",
"url": "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941"
],
[
"label": "Not a subtitle",
"url": "https://www.strem.io/images/addons/opensubtitles-logo.png"
]
]
]
let candidates = SubtitleCandidateParser.candidates(in: payload)
assertEqual(candidates.count, 1)
assertEqual(candidates[0].url.absoluteString, "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941")
assertEqual(candidates[0].label, "English")
assertEqual(candidates[0].language, "eng")
assert(SubtitleResolver.isDirectSubtitleFile(candidates[0].url), "Expected Stremio subtitle downloads to be attachable without another resolver hop")
}
private static func testOpenSubtitlesV3DownloadResponseResolution() {
let payload = """
{
"link": "https://dl.opensubtitles.org/en/download/subtitle.srt?token=secret",
"file_name": "episode.srt",
"requests": 1
}
""".data(using: .utf8)!
let original = SubtitleCandidate(
url: URL(string: "https://api.opensubtitles.com/api/v1/download")!,
label: "English",
language: "eng"
)
let candidate = SubtitleResolver.bestPlayableCandidate(
from: payload,
responseURL: original.url,
original: original
)
assertEqual(candidate?.url.absoluteString, "https://dl.opensubtitles.org/en/download/subtitle.srt?token=secret")
assertEqual(candidate?.label, "English")
assertEqual(candidate?.language, "eng")
}
private static func testOpenSubtitlesNestedDownloadResponseResolution() {
let payload = """
{
"data": {
"attributes": {
"files": [
{
"file_name": "ignored.txt",
"link": "https://cdn.example.test/ignored.txt"
},
{
"file_name": "episode.en.ass",
"download": {
"link": "https://dl.opensubtitles.org/en/download/episode.en.ass?token=secret"
}
}
]
}
}
}
""".data(using: .utf8)!
let original = SubtitleCandidate(
url: URL(string: "https://api.opensubtitles.com/api/v1/download/987")!,
label: "English SDH",
language: "eng"
)
let candidate = SubtitleResolver.bestPlayableCandidate(
from: payload,
responseURL: original.url,
original: original
)
assertEqual(candidate?.url.absoluteString, "https://dl.opensubtitles.org/en/download/episode.en.ass?token=secret")
assertEqual(candidate?.label, "English SDH")
assertEqual(candidate?.language, "eng")
}
private static func testSubtitleResolverDownloadJSONReturningLink() async {
MockURLProtocol.handlers = [
"https://api.opensubtitles.com/api/v1/download/123": (
200,
URL(string: "https://api.opensubtitles.com/api/v1/download/123")!,
#"{"link":"https://dl.opensubtitles.org/en/download/movie.srt?token=secret"}"#.data(using: .utf8)!
)
]
let resolver = SubtitleResolver(session: mockSession())
let candidate = await resolver.resolve(SubtitleCandidate(
url: URL(string: "https://api.opensubtitles.com/api/v1/download/123")!,
label: "English",
language: "eng"
))
assertEqual(candidate?.url.absoluteString, "https://dl.opensubtitles.org/en/download/movie.srt?token=secret")
assertEqual(candidate?.label, "English")
assertEqual(candidate?.language, "eng")
}
private static func testSubtitleResolverRedirectToDirectSubtitle() async {
MockURLProtocol.handlers = [
"https://api.opensubtitles.com/api/v1/download/redirect": (
200,
URL(string: "https://dl.opensubtitles.org/en/redirected.vtt?download=1")!,
Data()
)
]
let resolver = SubtitleResolver(session: mockSession())
let candidate = await resolver.resolve(SubtitleCandidate(
url: URL(string: "https://api.opensubtitles.com/api/v1/download/redirect")!,
label: "English",
language: "eng"
))
assertEqual(candidate?.url.absoluteString, "https://dl.opensubtitles.org/en/redirected.vtt?download=1")
}
private static func testSubtitleResolverRejectsNonSubtitleAPIResponse() async {
MockURLProtocol.handlers = [
"https://api.opensubtitles.com/api/v1/download/not-found": (
200,
URL(string: "https://api.opensubtitles.com/api/v1/download/not-found")!,
#"{"message":"not found"}"#.data(using: .utf8)!
)
]
let resolver = SubtitleResolver(session: mockSession())
let candidate = await resolver.resolve(SubtitleCandidate(
url: URL(string: "https://api.opensubtitles.com/api/v1/download/not-found")!,
label: "English",
language: "eng"
))
assert(candidate == nil, "Expected non-subtitle API response to be rejected")
}
private static func testSubtitleCandidateDeduplicationPreservesLabels() {
let payload: [String: Any] = [
"subtitles": [
[
"label": "English SDH",
"lang": "eng",
"url": "https://opensubtitles.example.test/download/duplicate.srt"
],
[
"label": "Duplicate",
"language": "English",
"download": "https://opensubtitles.example.test/download/duplicate.srt"
],
"https://opensubtitles.example.test/download/duplicate.srt"
]
]
let candidates = SubtitleCandidateParser.candidates(in: payload)
assertEqual(candidates.count, 1)
assertEqual(candidates[0].label, "English SDH")
assertEqual(candidates[0].language, "eng")
}
private static func testSubtitleCandidateDeduplicationUpgradesLabels() {
let payload: [String: Any] = [
"subtitles": [
"https://opensubtitles.example.test/download/duplicate.srt",
[
"label": "English SDH",
"lang": "eng",
"url": "https://opensubtitles.example.test/download/duplicate.srt"
]
]
]
let candidates = SubtitleCandidateParser.candidates(in: payload)
assertEqual(candidates.count, 1)
assertEqual(candidates[0].label, "English SDH")
assertEqual(candidates[0].language, "eng")
}
private static func testSubtitleOptionMappingIncludesNone() {
let options = SubtitleOptionMapper.options(from: [
SubtitleTrack(id: 2, name: "English"),
SubtitleTrack(id: 5, name: "Spanish")
])
assertEqual(options.map(\.name), ["None", "English", "Spanish"])
assertEqual(options.first?.id, -1)
}
private static func testSubtitleDisplayNameNormalization() {
assertEqual(
SubtitleDisplayName.displayName(for: SubtitleCandidate(
url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,
label: "Track 1",
language: "eng"
)),
"English"
)
assertEqual(
SubtitleDisplayName.displayName(for: SubtitleCandidate(
url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,
label: "Track 2",
language: "Spanish"
)),
"Spanish"
)
assertEqual(
SubtitleDisplayName.displayName(for: SubtitleCandidate(
url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,
label: "English SDH",
language: "eng"
)),
"English SDH"
)
assertEqual(
SubtitleDisplayName.displayName(for: SubtitleCandidate(
url: URL(string: "https://cdn.example.test/subtitles/movie.es.srt")!,
label: "External Subtitle",
language: nil
)),
"movie.es"
)
assertEqual(
SubtitleDisplayName.displayName(for: SubtitleCandidate(
url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,
label: "Track 3",
language: "nld"
)),
"Dutch"
)
assertEqual(
SubtitleDisplayName.displayName(for: SubtitleCandidate(
url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,
label: "Track 4",
language: "dan"
)),
"Danish"
)
}
private static func testSubtitleDisplayNameUsesPreservedNamesForGenericVLCTracks() {
let options = SubtitleOptionMapper.options(from: [
SubtitleTrack(
id: 3,
name: SubtitleDisplayName.name(forVLCTrackName: "Track 1", preservedName: "English")
),
SubtitleTrack(
id: 4,
name: SubtitleDisplayName.name(forVLCTrackName: "Commentary", preservedName: "Spanish")
)
])
assertEqual(options.map(\.name), ["None", "English", "Commentary"])
}
private static func assertEqual<T: Equatable>(_ actual: T?, _ expected: T, file: StaticString = #file, line: UInt = #line) {
assert(actual == expected, "Expected \(String(describing: expected)), got \(String(describing: actual))", file: file, line: line)
}
private static func mockSession() -> URLSession {
let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [MockURLProtocol.self]
return URLSession(configuration: configuration)
}
}
private final class MockURLProtocol: URLProtocol {
static var handlers: [String: (status: Int, url: URL, data: Data)] = [:]
override class func canInit(with request: URLRequest) -> Bool {
true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
request
}
override func startLoading() {
guard let url = request.url,
let handler = Self.handlers[url.absoluteString],
let response = HTTPURLResponse(
url: handler.url,
statusCode: handler.status,
httpVersion: "HTTP/1.1",
headerFields: nil
)
else {
client?.urlProtocol(self, didFailWithError: URLError(.badURL))
return
}
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: handler.data)
client?.urlProtocolDidFinishLoading(self)
}
override func stopLoading() {}
}