import Foundation
@main
struct StreamResolverTests {
static func main() async {
testClassifierPrefersObservedDirectFile()
testResolverSelectsUnsupportedDirectURLAndHeaders()
testResolverRejectsHLSOnlyResponse()
testRedactorHandlesPercentEncodedPath()
testPlaybackTimeFormatting()
testSubtitleCandidateParsing()
testOpenSubtitlesV3CandidateParsing()
testOpenSubtitlesNestedAttributesFilesParsing()
testOpenSubtitlesManifestIDsAreNotResolvedAsSubtitles()
testOpenSubtitlesArtworkAndAddonEndpointsAreIgnored()
testStremioSubtitleDownloadURLParsing()
testOpenSubtitlesV3DownloadResponseResolution()
testOpenSubtitlesNestedDownloadResponseResolution()
await testSubtitleResolverCachesStremioDownloadBody()
await testSubtitleResolverCachesPlainStremioDownloadBody()
await testSubtitleResolverDownloadJSONReturningLink()
await testSubtitleResolverRedirectToDirectSubtitle()
await testSubtitleResolverRejectsNonSubtitleAPIResponse()
testSubtitleCandidateDeduplicationPreservesLabels()
testSubtitleCandidateDeduplicationUpgradesLabels()
testSubtitleDisplayNameNormalization()
testSubtitleDisplayNameUsesPreservedNamesForGenericVLCTracks()
testSubtitleOptionMappingIncludesNone()
testExternalSubtitleParserHandlesCRLFSRT()
testExternalSubtitleCueLookupBoundaries()
testExternalSubtitleParserCleansMultilineCueText()
testExternalSubtitleParserHandlesSouthParkFirstCueTiming()
testContentRangeParsing()
testSparseRangeStoreMergesOverlaps()
testSparseRangeStoreHitPartialHitAndMiss()
testSparseRangeStoreEvictsOutsideWindow()
testSparseRangeStoreTrimsOverlappingWindow()
testSparseRangeStoreEvictsByBudgetWhilePreservingUsefulRanges()
testRangeCacheSessionCapsResponseRange()
testRangeCachePrefetchPrioritizesSeekOffset()
testRangeCacheSeekPrimingIncludesObservedVLCStart()
testRangeCachePrefetchUsesGlobalChunkBoundaries()
testRangeCacheForegroundMissFetchesAlignedChunks()
await testRangeCacheForegroundMissReprioritizesPrefetch()
await testRangeCacheHitFollowsActualPostSeekReadArea()
await testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges()
await testRangeProbeAppliesRequestTimeout()
await testRangeProbeFallsBackWhenServerIgnoresRange()
await testRangeFetcherPreservesHeaders()
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 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 resolved before VLC attachment")
}
private static func testContentRangeParsing() {
let parsed = HTTPContentRange.parse("bytes 10-19/100")
assertEqual(parsed?.range.start, 10)
assertEqual(parsed?.range.end, 19)
assertEqual(parsed?.totalLength, 100)
assert(HTTPContentRange.parse("items 10-19/100") == nil, "Expected non-byte content range to be rejected")
assert(HTTPContentRange.parse("bytes 20-10/100") == nil, "Expected invalid content range to be rejected")
}
private static func testSparseRangeStoreMergesOverlaps() {
let store = SparseHTTPByteRangeStore()
store.insert(data: Data([0, 1, 2, 3]), at: 0)
store.insert(data: Data([3, 4, 5]), at: 3)
assertEqual(store.cachedRanges, [HTTPByteRange(start: 0, end: 5)])
assertEqual(Array(store.data(for: HTTPByteRange(start: 0, end: 5)) ?? Data()), [0, 1, 2, 3, 4, 5])
}
private static func testSparseRangeStoreHitPartialHitAndMiss() {
let store = SparseHTTPByteRangeStore()
store.insert(data: Data([10, 11, 12, 13]), at: 10)
assertEqual(Array(store.data(for: HTTPByteRange(start: 10, end: 13)) ?? Data()), [10, 11, 12, 13])
assert(store.data(for: HTTPByteRange(start: 11, end: 14)) == nil, "Expected partial hit to miss")
assert(store.data(for: HTTPByteRange(start: 20, end: 21)) == nil, "Expected uncached range to miss")
}
private static func testSparseRangeStoreEvictsOutsideWindow() {
let store = SparseHTTPByteRangeStore()
store.insert(data: Data([0, 1, 2]), at: 0)
store.insert(data: Data([10, 11, 12]), at: 10)
store.evict(keeping: HTTPByteRange(start: 9, end: 12))
assertEqual(store.cachedRanges, [HTTPByteRange(start: 10, end: 12)])
}
private static func testSparseRangeStoreTrimsOverlappingWindow() {
let store = SparseHTTPByteRangeStore()
store.insert(data: Data([0, 1, 2, 3, 4, 5]), at: 0)
store.evict(keeping: HTTPByteRange(start: 2, end: 4))
assertEqual(store.cachedRanges, [HTTPByteRange(start: 2, end: 4)])
assertEqual(Array(store.data(for: HTTPByteRange(start: 2, end: 4)) ?? Data()), [2, 3, 4])
assert(store.data(for: HTTPByteRange(start: 0, end: 5)) == nil, "Expected trimmed bytes outside the window to be evicted")
}
private static func testSparseRangeStoreEvictsByBudgetWhilePreservingUsefulRanges() {
let store = SparseHTTPByteRangeStore()
store.insert(data: Data(repeating: 1, count: 4), at: 0)
store.insert(data: Data(repeating: 2, count: 4), at: 100)
store.insert(data: Data(repeating: 3, count: 4), at: 200)
let evicted = store.evict(
toByteBudget: 8,
preserving: [
HTTPByteRange(start: 0, end: 3),
HTTPByteRange(start: 190, end: 210)
]
)
assertEqual(evicted, [HTTPByteRange(start: 100, end: 103)])
assertEqual(store.cachedRanges, [
HTTPByteRange(start: 0, end: 3),
HTTPByteRange(start: 200, end: 203)
])
}
private static func testRangeCacheSessionCapsResponseRange() {
let session = ProgressiveHTTPRangeCacheSession(
fetcher: HTTPRangeRemoteFetcher(url: URL(string: "https://example.test/video.mkv")!, headers: [:]),
contentLength: 711_080_522,
durationProvider: { 0 }
)
let responseRange = session.responseRange(for: HTTPByteRange(start: 0, end: 711_080_521))
assertEqual(responseRange, HTTPByteRange(start: 0, end: 1_048_575))
}
private static func testRangeCachePrefetchPrioritizesSeekOffset() {
let session = ProgressiveHTTPRangeCacheSession(
fetcher: HTTPRangeRemoteFetcher(url: URL(string: "https://example.test/video.mkv")!, headers: [:]),
contentLength: 20_000_000,
durationProvider: { 0 }
)
let chunks = session.prefetchChunks(
in: HTTPByteRange(start: 0, end: 4_194_303),
preferredOffset: 2_200_000
)
assertEqual(chunks.prefix(2).map { $0 }, [
HTTPByteRange(start: 2_097_152, end: 3_145_727),
HTTPByteRange(start: 3_145_728, end: 4_194_303)
])
assertEqual(chunks.suffix(2).map { $0 }, [
HTTPByteRange(start: 0, end: 1_048_575),
HTTPByteRange(start: 1_048_576, end: 2_097_151)
])
}
private static func testRangeCacheSeekPrimingIncludesObservedVLCStart() {
let session = ProgressiveHTTPRangeCacheSession(
fetcher: HTTPRangeRemoteFetcher(url: URL(string: "https://example.test/video.mkv")!, headers: [:]),
contentLength: 711_080_522,
durationProvider: { 0 }
)
let estimatedOffset: Int64 = 213_615_760
let firstVLCRequest = HTTPByteRange(start: 212_942_432, end: 213_991_007)
let window = session.seekPrimeWindow(aroundByteOffset: estimatedOffset)
let chunks = session.prefetchChunks(
in: window,
preferredOffset: estimatedOffset,
startsAtWindowStart: true
)
let chunkContainingVLCStart = chunks.firstIndex { $0.contains(firstVLCRequest.start) }
let chunkContainingEstimatedOffset = chunks.firstIndex { $0.contains(estimatedOffset) }
assert(chunkContainingVLCStart != nil, "Expected seek priming to include VLC's first request start")
assert(chunkContainingEstimatedOffset != nil, "Expected seek priming to include the estimated offset")
assert(
chunkContainingVLCStart! <= chunkContainingEstimatedOffset!,
"Expected bytes behind the seek target to be primed before ahead chunks"
)
assertEqual(chunks[chunkContainingVLCStart!], HTTPByteRange(start: 212_860_928, end: 213_909_503))
}
private static func testRangeCachePrefetchUsesGlobalChunkBoundaries() {
let session = ProgressiveHTTPRangeCacheSession(
fetcher: HTTPRangeRemoteFetcher(url: URL(string: "https://example.test/video.mkv")!, headers: [:]),
contentLength: 711_080_522,
durationProvider: { 0 }
)
let chunks = session.prefetchChunks(
in: HTTPByteRange(start: 213_278_260, end: 216_000_000),
preferredOffset: 213_615_760
)
assert(chunks.allSatisfy { $0.start % 1_048_576 == 0 }, "Expected prefetch chunk starts to use stable global 1 MB boundaries: \(chunks)")
assertEqual(chunks[0], HTTPByteRange(start: 212_860_928, end: 213_909_503))
}
private static func testRangeCacheForegroundMissFetchesAlignedChunks() {
let session = ProgressiveHTTPRangeCacheSession(
fetcher: HTTPRangeRemoteFetcher(url: URL(string: "https://example.test/video.mkv")!, headers: [:]),
contentLength: 711_080_522,
durationProvider: { 0 }
)
let chunks = session.alignedChunks(covering: HTTPByteRange(start: 48_234_649, end: 49_185_907))
assertEqual(chunks, [HTTPByteRange(start: 48_234_496, end: 49_283_071)])
}
private static func testRangeCacheForegroundMissReprioritizesPrefetch() async {
let queue = DispatchQueue(label: "dreamio.range-cache-test")
var requestedRanges: [String] = []
MockURLProtocol.handler = { request in
let range = request.value(forHTTPHeaderField: "Range") ?? ""
queue.sync {
requestedRanges.append(range)
}
let byteRange = byteRange(fromHeader: range, contentLength: 80_000_000)
let response = HTTPURLResponse(
url: request.url!,
statusCode: 206,
httpVersion: nil,
headerFields: ["Content-Range": "bytes \(byteRange.start)-\(byteRange.end)/80000000"]
)!
return (Data(repeating: 1, count: Int(byteRange.length)), response)
}
let session = ProgressiveHTTPRangeCacheSession(
fetcher: HTTPRangeRemoteFetcher(
url: URL(string: "https://cdn.example.test/movie.mp4")!,
headers: [:],
session: mockSession()
),
contentLength: 80_000_000,
durationProvider: { 100 }
)
defer {
session.cancelPrefetch()
}
session.prefetch(aroundByteOffset: 28_242_716)
_ = try? await session.data(for: HTTPByteRange(start: 51_818_977, end: 52_867_552))
try? await Task.sleep(nanoseconds: 50_000_000)
let ranges = queue.sync { requestedRanges }
assert(ranges.contains("bytes=51380224-52428799"), "Expected foreground VLC miss to fetch aligned cache chunks")
assert(ranges.contains { range in
range.hasPrefix("bytes=52428800-")
}, "Expected prefetch to restart on a global chunk boundary near VLC's foreground range, got \(ranges)")
session.cancelPrefetch()
MockURLProtocol.handler = nil
try? await Task.sleep(nanoseconds: 50_000_000)
}
private static func testRangeCacheHitFollowsActualPostSeekReadArea() async {
let queue = DispatchQueue(label: "dreamio.range-cache-hit-follow-test")
var requestedRanges: [String] = []
MockURLProtocol.handler = { request in
let range = request.value(forHTTPHeaderField: "Range") ?? ""
queue.sync {
requestedRanges.append(range)
}
let byteRange = byteRange(fromHeader: range, contentLength: 80_000_000)
let response = HTTPURLResponse(
url: request.url!,
statusCode: 206,
httpVersion: nil,
headerFields: ["Content-Range": "bytes \(byteRange.start)-\(byteRange.end)/80000000"]
)!
return (Data(repeating: 1, count: Int(byteRange.length)), response)
}
let session = ProgressiveHTTPRangeCacheSession(
fetcher: HTTPRangeRemoteFetcher(
url: URL(string: "https://cdn.example.test/movie.mp4")!,
headers: [:],
session: mockSession()
),
contentLength: 80_000_000,
durationProvider: { 100 }
)
defer {
session.cancelPrefetch()
}
session.store.insert(data: Data(repeating: 7, count: 1_048_576), at: 27_165_812)
session.prefetchForSeek(aroundByteOffset: 15_936_567)
_ = try? await session.data(for: HTTPByteRange(start: 27_165_812, end: 28_214_387))
try? await Task.sleep(nanoseconds: 100_000_000)
let ranges = queue.sync { requestedRanges }
assert(ranges.contains { range in
range.hasPrefix("bytes=27262976-")
}, "Expected a cache hit far from the seek estimate to restart prefetch near VLC's real read area, got \(ranges)")
MockURLProtocol.handler = nil
try? await Task.sleep(nanoseconds: 50_000_000)
}
private static func testRangeProbeAllowsRangeCacheForMKVWhenServerSupportsRanges() async {
var requestCount = 0
MockURLProtocol.handler = { request in
requestCount += 1
assertEqual(request.httpMethod, "HEAD")
let response = HTTPURLResponse(
url: request.url!,
statusCode: 200,
httpVersion: nil,
headerFields: [
"Accept-Ranges": "bytes",
"Content-Length": "20"
]
)!
return (Data(), response)
}
let fetcher = HTTPRangeRemoteFetcher(
url: URL(string: "https://cdn.example.test/show.mkv?token=secret")!,
headers: [:],
session: mockSession()
)
let probe = await fetcher.probe()
assertEqual(probe.isCacheable, true)
assertEqual(probe.contentLength, 20)
assertEqual(probe.fallbackReason, nil)
assertEqual(requestCount, 1)
MockURLProtocol.handler = nil
}
private static func testRangeProbeAppliesRequestTimeout() async {
MockURLProtocol.handler = { request in
assertEqual(request.timeoutInterval, 1.5)
let response = HTTPURLResponse(
url: request.url!,
statusCode: 200,
httpVersion: nil,
headerFields: [
"Accept-Ranges": "bytes",
"Content-Length": "20"
]
)!
return (Data(), response)
}
let fetcher = HTTPRangeRemoteFetcher(
url: URL(string: "https://cdn.example.test/show.mkv")!,
headers: [:],
session: mockSession()
)
let probe = await fetcher.probe(timeoutInterval: 1.5)
assertEqual(probe.isCacheable, true)
MockURLProtocol.handler = nil
}
private static func byteRange(fromHeader header: String, contentLength: Int64) -> HTTPByteRange {
let value = header.replacingOccurrences(of: "bytes=", with: "")
let pieces = value.split(separator: "-", maxSplits: 1).map(String.init)
guard pieces.count == 2,
let start = Int64(pieces[0]) else {
return HTTPByteRange(start: 0, end: 0)
}
let end = pieces[1].isEmpty ? contentLength - 1 : (Int64(pieces[1]) ?? contentLength - 1)
return HTTPByteRange(start: start, end: min(end, contentLength - 1))
}
private static func testRangeProbeFallsBackWhenServerIgnoresRange() async {
MockURLProtocol.handler = { request in
if request.httpMethod == "HEAD" {
let response = HTTPURLResponse(
url: request.url!,
statusCode: 200,
httpVersion: nil,
headerFields: ["Content-Length": "4"]
)!
return (Data(), response)
}
assertEqual(request.value(forHTTPHeaderField: "Range"), "bytes=0-0")
let response = HTTPURLResponse(
url: request.url!,
statusCode: 200,
httpVersion: nil,
headerFields: ["Content-Length": "4"]
)!
return (Data([1, 2, 3, 4]), response)
}
let fetcher = HTTPRangeRemoteFetcher(
url: URL(string: "https://cdn.example.test/movie.mp4")!,
headers: [:],
session: mockSession()
)
let probe = await fetcher.probe()
assertEqual(probe.isCacheable, false)
assertEqual(probe.fallbackReason, "range-probe-status-200")
}
private static func testRangeFetcherPreservesHeaders() async {
MockURLProtocol.handler = { request in
assertEqual(request.value(forHTTPHeaderField: "User-Agent"), "DreamioTest/1")
assertEqual(request.value(forHTTPHeaderField: "Referer"), "https://web.stremio.com/")
assertEqual(request.value(forHTTPHeaderField: "Cookie"), "session=abc")
assertEqual(request.value(forHTTPHeaderField: "Range"), "bytes=5-7")
let response = HTTPURLResponse(
url: request.url!,
statusCode: 206,
httpVersion: nil,
headerFields: ["Content-Range": "bytes 5-7/20"]
)!
return (Data([5, 6, 7]), response)
}
let fetcher = HTTPRangeRemoteFetcher(
url: URL(string: "https://cdn.example.test/movie.mp4")!,
headers: [
"User-Agent": "DreamioTest/1",
"Referer": "https://web.stremio.com/",
"Cookie": "session=abc"
],
session: mockSession()
)
let data = try? await fetcher.fetch(range: HTTPByteRange(start: 5, end: 7))
assertEqual(Array(data ?? Data()), [5, 6, 7])
}
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 testSubtitleResolverCachesStremioDownloadBody() async {
let sourceURL = "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941"
let subtitleBody = """
1
00:00:01,000 --> 00:00:02,000
Hello from Stremio
"""
MockURLProtocol.handler = nil
MockURLProtocol.handlers = [
sourceURL: (
200,
URL(string: sourceURL)!,
subtitleBody.data(using: .utf8)!
)
]
let cacheDirectory = FileManager.default.temporaryDirectory
.appendingPathComponent("DreamioSubtitleResolverTests-\(UUID().uuidString)", isDirectory: true)
defer {
try? FileManager.default.removeItem(at: cacheDirectory)
}
let resolver = SubtitleResolver(session: mockSession(), cacheDirectory: cacheDirectory)
let candidate = await resolver.resolve(SubtitleCandidate(
url: URL(string: sourceURL)!,
label: "English",
language: "eng"
))
assertEqual(candidate?.url.isFileURL, true)
assertEqual(candidate?.url.pathExtension, "srt")
assertEqual(candidate?.label, "English")
assertEqual(candidate?.language, "eng")
let cachedBody = try? String(contentsOf: candidate!.url, encoding: .utf8)
assertEqual(cachedBody, subtitleBody)
}
private static func testSubtitleResolverCachesPlainStremioDownloadBody() async {
let sourceURL = "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341942"
let subtitleBody = """
00:01.000 --> 00:02.000
Plain cue text without an index
"""
MockURLProtocol.handler = nil
MockURLProtocol.handlers = [
sourceURL: (
200,
URL(string: sourceURL)!,
subtitleBody.data(using: .utf8)!
)
]
let cacheDirectory = FileManager.default.temporaryDirectory
.appendingPathComponent("DreamioSubtitleResolverTests-\(UUID().uuidString)", isDirectory: true)
defer {
try? FileManager.default.removeItem(at: cacheDirectory)
}
let resolver = SubtitleResolver(session: mockSession(), cacheDirectory: cacheDirectory)
let candidate = await resolver.resolve(SubtitleCandidate(
url: URL(string: sourceURL)!,
label: "English",
language: "eng"
))
assertEqual(candidate?.url.isFileURL, true)
assertEqual(candidate?.url.pathExtension, "srt")
let cachedBody = try? String(contentsOf: candidate!.url, encoding: .utf8)
assertEqual(cachedBody, subtitleBody)
}
private static func testSubtitleResolverDownloadJSONReturningLink() async {
MockURLProtocol.handler = nil
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.handler = nil
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.handler = nil
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 testExternalSubtitleParserHandlesCRLFSRT() {
let body = "1\r\n00:00:01,000 --> 00:00:02,500\r\nHello from CRLF\r\n\r\n"
let cues = ExternalSubtitleTrackParser.parseCues(from: body)
assertEqual(cues.count, 1)
assertEqual(cues[0].start, 1)
assertEqual(cues[0].end, 2.5)
assertEqual(cues[0].text, "Hello from CRLF")
}
private static func testExternalSubtitleCueLookupBoundaries() {
let track = ExternalSubtitleTrack(
id: 1,
name: "English",
cues: [
ExternalSubtitleCue(start: 7.101, end: 9.25, text: "First cue")
]
)
assert(track.cue(at: 7.100) == nil, "Expected time before first cue to hide overlay")
assertEqual(track.cue(at: 7.101)?.text, "First cue")
assertEqual(track.cue(at: 8.0)?.text, "First cue")
assert(track.cue(at: 9.25) == nil, "Expected cue end boundary to hide overlay")
assert(track.cue(at: 9.251) == nil, "Expected time after cue end to hide overlay")
}
private static func testExternalSubtitleParserCleansMultilineCueText() {
let body = """
1
00:00:03,000 --> 00:00:05,000
Hello
{\\an8}there
"""
let cues = ExternalSubtitleTrackParser.parseCues(from: body)
assertEqual(cues.count, 1)
assertEqual(cues[0].text, "Hello\nthere")
}
private static func testExternalSubtitleParserHandlesSouthParkFirstCueTiming() {
let body = """
1
00:00:07,101 --> 00:00:09,103
I'm going down to South Park
"""
let cues = ExternalSubtitleTrackParser.parseCues(from: body)
let track = ExternalSubtitleTrack(id: 1, name: "English", cues: cues)
assertEqual(cues.count, 1)
assertEqual(cues[0].start, 7.101)
assert(track.cue(at: 7.100) == nil, "Expected no text before the South Park-style first cue")
assertEqual(track.cue(at: 7.101)?.text, "I'm going down to South Park")
}
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(_ 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 handler: ((URLRequest) throws -> (Data, HTTPURLResponse))?
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() {
if let handler = Self.handler {
do {
let (data, response) = try handler(request)
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: data)
client?.urlProtocolDidFinishLoading(self)
} catch {
client?.urlProtocol(self, didFailWithError: error)
}
return
}
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() {}
}