tv-anarchy/Tests/TVAnarchyCoreTests/TorrentDecoderTests.swift

98 lines
5.5 KiB
Swift
Raw Permalink Normal View History

import XCTest
@testable import TVAnarchyCore
/// Locks the torrent wire formats against real `cli.ts` output.
final class TorrentDecoderTests: XCTestCase {
func testTransferRowDecodeRPC() throws {
// Rich JSON-RPC shape from `cli.ts tx-list` (transmissionListRich).
let json = #"""
[{"id":1,"name":"Done.S05","percentDone":1,"status":6,"doneDate":1749329821,"addedDate":1749300000,
"haveValid":8760000000,"sizeWhenDone":8760000000,"rateDownload":0,"rateUpload":1024,"uploadRatio":0.5,"eta":-1},
{"id":11,"name":"WIP","percentDone":0.15,"status":4,"doneDate":0,"addedDate":1749329000,
"haveValid":300000000,"sizeWhenDone":2000000000,"rateDownload":1048576,"rateUpload":0,"uploadRatio":0,"eta":3720}]
"""#
let rows = try JSONDecoder().decode([TorrentRow].self, from: Data(json.utf8))
XCTAssertEqual(rows.count, 2)
let done = rows[0]
XCTAssertTrue(done.isComplete)
XCTAssertEqual(done.statusLabel, "Seeding")
XCTAssertNotNil(done.completedAt)
XCTAssertEqual(done.completedAt, Date(timeIntervalSince1970: 1749329821))
XCTAssertFalse(done.upText.isEmpty) // 1024 B/s formatted
let wip = rows[1]
XCTAssertFalse(wip.isComplete)
XCTAssertTrue(wip.isDownloading)
XCTAssertEqual(wip.progress, 0.15, accuracy: 0.001)
XCTAssertNil(wip.completedAt) // doneDate 0 not completed
XCTAssertEqual(wip.etaText, "1h 2m") // 3720s
}
func testRecencySort() {
func row(_ id: Int, done: Int, added: Int) -> TorrentRow {
let j = #"{"id":\#(id),"name":"t\#(id)","percentDone":\#(done > 0 ? 1 : 0),"status":0,"doneDate":\#(done),"addedDate":\#(added),"haveValid":0,"sizeWhenDone":0,"rateDownload":0,"rateUpload":0,"uploadRatio":0,"eta":-1}"#
return try! JSONDecoder().decode(TorrentRow.self, from: Data(j.utf8))
}
let unsorted = [row(1, done: 0, added: 50), row(2, done: 100, added: 10), row(3, done: 200, added: 20)]
let sorted = unsorted.sorted(by: DownloadsController.byRecency)
XCTAssertEqual(sorted.map(\.id), [3, 2, 1]) // newest-completed first; in-progress last
}
func testTransferRowDecodesHealthFields() throws {
// The list poll now also carries error/errorString/peersConnected (added to
// RPC_FIELDS) they're optional so a pre-Part-E cli still decodes, but when
// present they must drive the health classifier.
let json = #"""
[{"id":7,"name":"Stuck","percentDone":0.4,"status":4,"doneDate":0,"addedDate":1749300000,
"haveValid":1,"sizeWhenDone":10,"rateDownload":0,"rateUpload":0,"uploadRatio":0,"eta":-1,
"error":0,"errorString":"","peersConnected":0}]
"""#
let row = try JSONDecoder().decode([TorrentRow].self, from: Data(json.utf8))[0]
XCTAssertEqual(row.peersConnected, 0)
// 0 peers + 0 rate, stuck past the dead threshold dead.
XCTAssertEqual(row.health(secondsStuck: 400), .dead)
// Same row, errored takes precedence regardless of peers/time.
let errJSON = #"{"id":8,"name":"E","percentDone":0.4,"status":4,"doneDate":0,"addedDate":1,"haveValid":1,"sizeWhenDone":10,"rateDownload":0,"rateUpload":0,"uploadRatio":0,"eta":-1,"error":3,"errorString":"tracker gone","peersConnected":5}"#
let errored = try JSONDecoder().decode(TorrentRow.self, from: Data(errJSON.utf8))
XCTAssertEqual(errored.health(secondsStuck: 10), .errored)
}
func testTorrentDetailDecode() throws {
// Wire shape of `cli.ts tx-detail <id>` (transmissionDetail).
let json = #"""
{"id":11,"name":"WIP.S01","percentDone":0.15,"status":4,"error":0,"errorString":"",
"eta":3720,"rateDownload":1048576,"rateUpload":0,"uploadRatio":0,
"peersConnected":3,"peersSendingToUs":2,"peersGettingFromUs":1,
"downloadDir":"/bigdisk/_/media/tv",
"trackerStats":[
{"host":"udp://tracker.one:1337","announceState":1,"lastAnnounceResult":"Success","lastAnnounceSucceeded":true,"seederCount":42,"leecherCount":7},
{"host":"udp://dead.tracker:80","announceState":0,"lastAnnounceResult":"Could not connect to tracker","lastAnnounceSucceeded":false,"seederCount":-1,"leecherCount":-1}
]}
"""#
let d = try JSONDecoder().decode(TorrentDetail.self, from: Data(json.utf8))
XCTAssertEqual(d.id, 11)
XCTAssertFalse(d.isComplete)
XCTAssertFalse(d.hasError)
XCTAssertEqual(d.peersText, "3 connected · ↓2 ↑1")
XCTAssertEqual(d.trackerStats.count, 2)
XCTAssertTrue(d.trackerStats[0].lastAnnounceSucceeded)
XCTAssertEqual(d.trackerStats[0].swarmText, "42 seed · 7 leech")
XCTAssertEqual(d.trackerStats[1].swarmText, "— seed · — leech") // -1 unknown
XCTAssertEqual(d.health(), .downloading)
}
func testSearchResultDecodeAndAddable() throws {
let json = #"""
[{"filename":"Show.S01.1080p","source":"1337x","size":"3.2 GB","seeders":45,"leechers":3,"magnet":"magnet:?xt=urn:btih:abc"},
{"filename":"NoMagnet","source":"tpb","size":"1 GB","seeders":2,"leechers":0,"magnet":null}]
"""#
let results = try JSONDecoder().decode([TorrentResult].self, from: Data(json.utf8))
XCTAssertEqual(results.count, 2)
XCTAssertTrue(results[0].addable)
XCTAssertEqual(results[0].seeders, 45)
XCTAssertFalse(results[1].addable) // null magnet not addable
XCTAssertNotEqual(results[0].id, results[1].id)
}
}