702 lines
27 KiB
Rust
702 lines
27 KiB
Rust
use super::*;
|
|
|
|
#[test]
|
|
fn add_torrent_registers_runtime_backed_bt_metadata_surfaces() {
|
|
let torrent_payload = "ZDg6YW5ub3VuY2UzNTpodHRwOi8vdHJhY2tlci5leGFtcGxlLm9yZy9hbm5vdW5jZTQ6aW5mb2Q0Om5hbWUxMDp1YnVudHUuaXNvMTI6cGllY2UgbGVuZ3RoaTE2Mzg0ZTY6bGVuZ3RoaTMyNzY4ZTY6cGllY2VzNDA6YWFhYWFhYWFhYWFhYWFhYWFhYWFiYmJiYmJiYmJiYmJiYmJiYmJiYmVl";
|
|
let mut dispatcher = InProcessRpcDispatcher::new();
|
|
let add = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2AddTorrent.as_str().to_owned(),
|
|
params: vec![RpcValue::String(torrent_payload.to_owned())],
|
|
meta: Default::default(),
|
|
});
|
|
let gid = match add.result {
|
|
Some(RpcValue::String(gid)) => gid,
|
|
other => panic!("unexpected addTorrent result: {other:?}"),
|
|
};
|
|
|
|
let status = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2TellStatus.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
match status.result {
|
|
Some(RpcValue::Object(payload)) => {
|
|
assert_eq!(payload.get("isBt"), Some(&RpcValue::Bool(true)));
|
|
assert_eq!(payload.get("metadataOnly"), Some(&RpcValue::Bool(false)));
|
|
assert!(matches!(
|
|
payload.get("announceList"),
|
|
Some(RpcValue::Array(tiers)) if !tiers.is_empty()
|
|
));
|
|
assert!(matches!(
|
|
payload.get("magnetUri"),
|
|
Some(RpcValue::String(uri)) if uri.starts_with("magnet:?xt=urn:btih:")
|
|
));
|
|
}
|
|
other => panic!("unexpected tellStatus after addTorrent: {other:?}"),
|
|
}
|
|
|
|
let files = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2GetFiles.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
match files.result {
|
|
Some(RpcValue::Array(entries)) => match entries.first() {
|
|
Some(RpcValue::Object(file)) => {
|
|
assert_eq!(
|
|
file.get("path"),
|
|
Some(&RpcValue::String("ubuntu.iso".to_owned()))
|
|
);
|
|
assert_eq!(
|
|
file.get("length"),
|
|
Some(&RpcValue::String("32768".to_owned()))
|
|
);
|
|
}
|
|
other => panic!("unexpected getFiles entry after addTorrent: {other:?}"),
|
|
},
|
|
other => panic!("unexpected getFiles result after addTorrent: {other:?}"),
|
|
}
|
|
|
|
let servers = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2GetServers.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
let error = servers
|
|
.error
|
|
.expect("getServers should reject non-active BT downloads");
|
|
assert!(
|
|
error
|
|
.message
|
|
.contains(&format!("No active download for GID#{gid}"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn add_uri_magnet_registers_bt_runtime_status_surfaces() {
|
|
let magnet = "magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=bt-magnet.iso&tr=http%3A%2F%2Ftracker.example.org%2Fannounce";
|
|
let mut dispatcher = InProcessRpcDispatcher::new();
|
|
let add = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2AddUri.as_str().to_owned(),
|
|
params: vec![RpcValue::String(magnet.to_owned())],
|
|
meta: Default::default(),
|
|
});
|
|
let gid = match add.result {
|
|
Some(RpcValue::String(gid)) => gid,
|
|
other => panic!("unexpected addUri magnet result: {other:?}"),
|
|
};
|
|
|
|
let status = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2TellStatus.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid)],
|
|
meta: Default::default(),
|
|
});
|
|
match status.result {
|
|
Some(RpcValue::Object(payload)) => {
|
|
assert_eq!(payload.get("isBt"), Some(&RpcValue::Bool(true)));
|
|
assert_eq!(payload.get("metadataOnly"), Some(&RpcValue::Bool(true)));
|
|
assert!(matches!(
|
|
payload.get("magnetUri"),
|
|
Some(RpcValue::String(uri)) if uri.starts_with("magnet:?xt=urn:btih:")
|
|
));
|
|
assert!(matches!(
|
|
payload.get("announceList"),
|
|
Some(RpcValue::Array(tiers)) if !tiers.is_empty()
|
|
));
|
|
}
|
|
other => panic!("unexpected tellStatus after addUri magnet: {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn tracker_announce_ingestion_populates_peers_and_preserves_bt_servers() {
|
|
fn bencode_int(value: i64) -> Vec<u8> {
|
|
format!("i{value}e").into_bytes()
|
|
}
|
|
|
|
fn bencode_bytes(value: &[u8]) -> Vec<u8> {
|
|
let mut out = format!("{}:", value.len()).into_bytes();
|
|
out.extend_from_slice(value);
|
|
out
|
|
}
|
|
|
|
fn bencode_list(values: Vec<Vec<u8>>) -> Vec<u8> {
|
|
let mut out = vec![b'l'];
|
|
for value in values {
|
|
out.extend_from_slice(&value);
|
|
}
|
|
out.push(b'e');
|
|
out
|
|
}
|
|
|
|
fn bencode_dict(entries: Vec<(&str, Vec<u8>)>) -> Vec<u8> {
|
|
let mut out = vec![b'd'];
|
|
for (key, value) in entries {
|
|
out.extend_from_slice(format!("{}:{key}", key.len()).as_bytes());
|
|
out.extend_from_slice(&value);
|
|
}
|
|
out.push(b'e');
|
|
out
|
|
}
|
|
|
|
let magnet = "magnet:?xt=urn:btih:89abcdef0123456789abcdef0123456789abcdef&dn=bt-peers.iso&tr=http%3A%2F%2Ftracker.example.org%2Fannounce";
|
|
let mut dispatcher = InProcessRpcDispatcher::new();
|
|
let add = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2AddUri.as_str().to_owned(),
|
|
params: vec![RpcValue::String(magnet.to_owned())],
|
|
meta: Default::default(),
|
|
});
|
|
let gid = match add.result {
|
|
Some(RpcValue::String(gid)) => gid,
|
|
other => panic!("unexpected addUri magnet result: {other:?}"),
|
|
};
|
|
|
|
let announce_bytes = bencode_dict(vec![
|
|
("interval", bencode_int(1800)),
|
|
("tracker id", bencode_bytes(b"tracker-session-1")),
|
|
(
|
|
"peers",
|
|
bencode_list(vec![
|
|
bencode_dict(vec![
|
|
("ip", bencode_bytes(b"203.0.113.10")),
|
|
("port", bencode_int(51413)),
|
|
("peer id", bencode_bytes(b"-AZ2060-123456789012")),
|
|
("client", bencode_bytes(b"Azureus 2.0.6.0")),
|
|
("choked", bencode_int(0)),
|
|
("interested", bencode_int(1)),
|
|
]),
|
|
bencode_dict(vec![
|
|
("ip", bencode_bytes(b"203.0.113.11")),
|
|
("port", bencode_int(51414)),
|
|
("choked", bencode_int(1)),
|
|
("interested", bencode_int(0)),
|
|
]),
|
|
]),
|
|
),
|
|
]);
|
|
|
|
let announce = TrackerResponseModel::from_announce_bytes(&announce_bytes)
|
|
.expect("announce response should parse");
|
|
dispatcher
|
|
.apply_tracker_announce_result(&gid, &announce)
|
|
.expect("tracker announce should ingest");
|
|
|
|
let peers = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2GetPeers.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
match peers.result {
|
|
Some(RpcValue::Array(entries)) => {
|
|
assert!(!entries.is_empty(), "expected concrete peer entries");
|
|
match entries.first() {
|
|
Some(RpcValue::Object(peer)) => {
|
|
assert!(matches!(peer.get("ip"), Some(RpcValue::String(_))));
|
|
assert!(matches!(peer.get("port"), Some(RpcValue::String(_))));
|
|
}
|
|
other => panic!("unexpected getPeers entry: {other:?}"),
|
|
}
|
|
}
|
|
other => panic!("unexpected getPeers result: {other:?}"),
|
|
}
|
|
|
|
let servers = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2GetServers.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
let error = servers
|
|
.error
|
|
.expect("getServers should reject non-active BT downloads");
|
|
assert!(
|
|
error
|
|
.message
|
|
.contains(&format!("No active download for GID#{gid}"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn live_http_tracker_announce_round_trips_into_dispatcher_peer_visibility() {
|
|
use std::{
|
|
io::{Read, Write},
|
|
net::TcpListener,
|
|
thread,
|
|
};
|
|
|
|
let listener = TcpListener::bind("127.0.0.1:0").expect("local listener should bind");
|
|
let addr = listener.local_addr().expect("local addr should exist");
|
|
let payload = {
|
|
let mut payload = b"d8:intervali900e5:peers6:".to_vec();
|
|
payload.extend_from_slice(&[127, 0, 0, 1, 0x1A, 0xE1]);
|
|
payload.extend_from_slice(b"e");
|
|
payload
|
|
};
|
|
let handle = thread::spawn(move || {
|
|
let (mut stream, _) = listener.accept().expect("tracker client should connect");
|
|
let mut request = [0_u8; 2048];
|
|
let read = stream.read(&mut request).expect("request should read");
|
|
let request_text = String::from_utf8_lossy(&request[..read]);
|
|
assert!(request_text.starts_with("GET /announce?"));
|
|
assert!(request_text.contains("compact=1"));
|
|
let response = format!(
|
|
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: text/plain\r\n\r\n",
|
|
payload.len()
|
|
);
|
|
stream
|
|
.write_all(response.as_bytes())
|
|
.expect("headers should write");
|
|
stream.write_all(&payload).expect("payload should write");
|
|
});
|
|
|
|
let transport = ReqwestTrackerTransport::new().expect("reqwest tracker transport should build");
|
|
let announce = transport
|
|
.announce(&TrackerRequestModel {
|
|
announce_url: format!("http://{addr}/announce"),
|
|
info_hash: "0123456789abcdef0123456789abcdef01234567".to_owned(),
|
|
peer_id: "89abcdef0123456789abcdef0123456789abcdef".to_owned(),
|
|
port: 6881,
|
|
uploaded: 0,
|
|
downloaded: 0,
|
|
left: 2048,
|
|
event: Some("started".to_owned()),
|
|
compact: true,
|
|
numwant: Some(10),
|
|
})
|
|
.expect("live announce should succeed");
|
|
|
|
let magnet = "magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=bt-live-tracker.iso&tr=http%3A%2F%2Ftracker.example.org%2Fannounce";
|
|
let mut dispatcher = InProcessRpcDispatcher::new();
|
|
let add = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2AddUri.as_str().to_owned(),
|
|
params: vec![RpcValue::String(magnet.to_owned())],
|
|
meta: Default::default(),
|
|
});
|
|
let gid = match add.result {
|
|
Some(RpcValue::String(gid)) => gid,
|
|
other => panic!("unexpected addUri result: {other:?}"),
|
|
};
|
|
|
|
dispatcher
|
|
.apply_tracker_announce_result(&gid, &announce)
|
|
.expect("tracker announce should ingest");
|
|
|
|
let peers = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2GetPeers.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
match peers.result {
|
|
Some(RpcValue::Array(items)) => match items.first() {
|
|
Some(RpcValue::Object(peer)) => {
|
|
assert_eq!(
|
|
peer.get("ip"),
|
|
Some(&RpcValue::String("127.0.0.1".to_owned()))
|
|
);
|
|
assert_eq!(peer.get("port"), Some(&RpcValue::String("6881".to_owned())));
|
|
}
|
|
other => panic!("unexpected peer payload after live tracker announce: {other:?}"),
|
|
},
|
|
other => panic!("unexpected getPeers result after live tracker announce: {other:?}"),
|
|
}
|
|
|
|
handle.join().expect("tracker server thread should join");
|
|
}
|
|
|
|
#[test]
|
|
fn bt_change_option_select_file_round_trips_through_get_files_selected_flags() {
|
|
let torrent_payload = "ZDg6YW5ub3VuY2UzNTpodHRwOi8vdHJhY2tlci5leGFtcGxlLm9yZy9hbm5vdW5jZTQ6aW5mb2Q0Om5hbWUxMDp1YnVudHUuaXNvMTI6cGllY2UgbGVuZ3RoaTE2Mzg0ZTY6bGVuZ3RoaTMyNzY4ZTY6cGllY2VzNDA6YWFhYWFhYWFhYWFhYWFhYWFhYWFiYmJiYmJiYmJiYmJiYmJiYmJiYmVl";
|
|
let mut dispatcher = InProcessRpcDispatcher::new();
|
|
let add = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2AddTorrent.as_str().to_owned(),
|
|
params: vec![RpcValue::String(torrent_payload.to_owned())],
|
|
meta: Default::default(),
|
|
});
|
|
let gid = match add.result {
|
|
Some(RpcValue::String(gid)) => gid,
|
|
other => panic!("unexpected addTorrent result: {other:?}"),
|
|
};
|
|
|
|
let change = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2ChangeOption.as_str().to_owned(),
|
|
params: vec![
|
|
RpcValue::String(gid.clone()),
|
|
RpcValue::Object(BTreeMap::from([(
|
|
"select-file".to_owned(),
|
|
RpcValue::String(String::new()),
|
|
)])),
|
|
],
|
|
meta: Default::default(),
|
|
});
|
|
assert!(
|
|
change.error.is_some(),
|
|
"empty select-file value should be rejected to protect BT file selection contract"
|
|
);
|
|
|
|
let change = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2ChangeOption.as_str().to_owned(),
|
|
params: vec![
|
|
RpcValue::String(gid.clone()),
|
|
RpcValue::Object(BTreeMap::from([(
|
|
"select-file".to_owned(),
|
|
RpcValue::String("1".to_owned()),
|
|
)])),
|
|
],
|
|
meta: Default::default(),
|
|
});
|
|
assert_eq!(change.result, Some(RpcValue::String("OK".to_owned())));
|
|
|
|
let files = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2GetFiles.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid)],
|
|
meta: Default::default(),
|
|
});
|
|
match files.result {
|
|
Some(RpcValue::Array(entries)) => {
|
|
assert_eq!(entries.len(), 1, "fixture torrent currently has one file");
|
|
match entries.first() {
|
|
Some(RpcValue::Object(file)) => {
|
|
assert_eq!(
|
|
file.get("selected"),
|
|
Some(&RpcValue::String("true".to_owned()))
|
|
);
|
|
}
|
|
other => panic!("unexpected getFiles entry: {other:?}"),
|
|
}
|
|
}
|
|
other => panic!("unexpected getFiles response: {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn bt_pause_and_selected_state_are_persisted_in_saved_session_file() {
|
|
let session_root = std::env::temp_dir().join(format!(
|
|
"aria2-rust-pro-tests-bt-session-{}",
|
|
std::process::id()
|
|
));
|
|
let _ = std::fs::create_dir_all(&session_root);
|
|
let session_path = session_root.join("session.txt");
|
|
let mut dispatcher = InProcessRpcDispatcher::with_runtime(
|
|
RuntimeConfig::default().with_session_path(session_path.clone()),
|
|
);
|
|
let torrent_payload = "ZDg6YW5ub3VuY2UzNTpodHRwOi8vdHJhY2tlci5leGFtcGxlLm9yZy9hbm5vdW5jZTQ6aW5mb2Q0Om5hbWUxMDp1YnVudHUuaXNvMTI6cGllY2UgbGVuZ3RoaTE2Mzg0ZTY6bGVuZ3RoaTMyNzY4ZTY6cGllY2VzNDA6YWFhYWFhYWFhYWFhYWFhYWFhYWFiYmJiYmJiYmJiYmJiYmJiYmJiYmVl";
|
|
let add = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2AddTorrent.as_str().to_owned(),
|
|
params: vec![RpcValue::String(torrent_payload.to_owned())],
|
|
meta: Default::default(),
|
|
});
|
|
let gid = match add.result {
|
|
Some(RpcValue::String(gid)) => gid,
|
|
other => panic!("unexpected addTorrent result: {other:?}"),
|
|
};
|
|
|
|
let _ = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2ChangeOption.as_str().to_owned(),
|
|
params: vec![
|
|
RpcValue::String(gid.clone()),
|
|
RpcValue::Object(BTreeMap::from([(
|
|
"select-file".to_owned(),
|
|
RpcValue::String("1".to_owned()),
|
|
)])),
|
|
],
|
|
meta: Default::default(),
|
|
});
|
|
let pause = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2Pause.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
assert_eq!(pause.result, Some(RpcValue::String(gid.clone())));
|
|
|
|
let save = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2SaveSession.as_str().to_owned(),
|
|
params: vec![],
|
|
meta: Default::default(),
|
|
});
|
|
assert_eq!(save.result, Some(RpcValue::String("OK".to_owned())));
|
|
|
|
let loaded = load_session_file(&session_path).expect("saved session file should load");
|
|
let persisted = loaded
|
|
.entries
|
|
.iter()
|
|
.find(|entry| entry.gid == gid)
|
|
.expect("saved session should include paused BT gid");
|
|
let metadata = persisted
|
|
.metadata
|
|
.as_ref()
|
|
.expect("session entry should include metadata");
|
|
assert_eq!(metadata.get("status"), Some(&"paused".to_owned()));
|
|
assert_eq!(
|
|
metadata.get("bt.file.0.selected"),
|
|
Some(&"true".to_owned()),
|
|
"session metadata should preserve BT selected-file flag for reload path"
|
|
);
|
|
|
|
let _ = std::fs::remove_file(session_path);
|
|
let _ = std::fs::remove_dir_all(session_root);
|
|
}
|
|
|
|
#[test]
|
|
fn bt_metadata_only_must_not_report_false_completion_lengths() {
|
|
let magnet = "magnet:?xt=urn:btih:fedcba98765432100123456789abcdef01234567&dn=bt-metadata-only.iso&tr=http%3A%2F%2Ftracker.example.org%2Fannounce";
|
|
let mut dispatcher = InProcessRpcDispatcher::new();
|
|
let add = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2AddUri.as_str().to_owned(),
|
|
params: vec![RpcValue::String(magnet.to_owned())],
|
|
meta: Default::default(),
|
|
});
|
|
let gid = match add.result {
|
|
Some(RpcValue::String(gid)) => gid,
|
|
other => panic!("unexpected addUri magnet result: {other:?}"),
|
|
};
|
|
|
|
let status = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2TellStatus.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid)],
|
|
meta: Default::default(),
|
|
});
|
|
match status.result {
|
|
Some(RpcValue::Object(payload)) => {
|
|
assert_eq!(payload.get("isBt"), Some(&RpcValue::Bool(true)));
|
|
assert_eq!(payload.get("metadataOnly"), Some(&RpcValue::Bool(true)));
|
|
assert_ne!(
|
|
payload.get("status"),
|
|
Some(&RpcValue::String("complete".to_owned())),
|
|
"metadata-only BT should not look fully complete before payload download"
|
|
);
|
|
assert_eq!(
|
|
payload.get("completedLength"),
|
|
Some(&RpcValue::String("0".to_owned())),
|
|
"metadata-only BT should not fake payload completed length"
|
|
);
|
|
}
|
|
other => panic!("unexpected tellStatus payload: {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn bt_select_file_pause_resume_and_save_session_contract_is_stable() {
|
|
let session_root = std::env::temp_dir().join(format!(
|
|
"aria2-rust-pro-tests-bt-select-pause-resume-{}",
|
|
std::process::id()
|
|
));
|
|
let _ = std::fs::create_dir_all(&session_root);
|
|
let session_path = session_root.join("session.txt");
|
|
let mut dispatcher = InProcessRpcDispatcher::with_runtime(
|
|
RuntimeConfig::default().with_session_path(session_path.clone()),
|
|
);
|
|
let torrent_payload = "ZDg6YW5ub3VuY2UzNTpodHRwOi8vdHJhY2tlci5leGFtcGxlLm9yZy9hbm5vdW5jZTQ6aW5mb2Q0Om5hbWUxMDp1YnVudHUuaXNvMTI6cGllY2UgbGVuZ3RoaTE2Mzg0ZTY6bGVuZ3RoaTMyNzY4ZTY6cGllY2VzNDA6YWFhYWFhYWFhYWFhYWFhYWFhYWFiYmJiYmJiYmJiYmJiYmJiYmJiYmVl";
|
|
let add = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2AddTorrent.as_str().to_owned(),
|
|
params: vec![RpcValue::String(torrent_payload.to_owned())],
|
|
meta: Default::default(),
|
|
});
|
|
let gid = match add.result {
|
|
Some(RpcValue::String(gid)) => gid,
|
|
other => panic!("unexpected addTorrent result: {other:?}"),
|
|
};
|
|
|
|
let change = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2ChangeOption.as_str().to_owned(),
|
|
params: vec![
|
|
RpcValue::String(gid.clone()),
|
|
RpcValue::Object(BTreeMap::from([(
|
|
"select-file".to_owned(),
|
|
RpcValue::String("1".to_owned()),
|
|
)])),
|
|
],
|
|
meta: Default::default(),
|
|
});
|
|
assert_eq!(change.result, Some(RpcValue::String("OK".to_owned())));
|
|
|
|
let pause = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2Pause.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
assert!(pause.error.is_none());
|
|
|
|
let unpause = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2Unpause.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
assert!(unpause.error.is_none());
|
|
|
|
let repause = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2Pause.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
assert!(repause.error.is_none());
|
|
|
|
let files = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2GetFiles.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid.clone())],
|
|
meta: Default::default(),
|
|
});
|
|
match files.result {
|
|
Some(RpcValue::Array(entries)) => match entries.first() {
|
|
Some(RpcValue::Object(file)) => assert_eq!(
|
|
file.get("selected"),
|
|
Some(&RpcValue::String("true".to_owned()))
|
|
),
|
|
other => panic!("unexpected getFiles entry after pause/resume: {other:?}"),
|
|
},
|
|
other => panic!("unexpected getFiles payload after pause/resume: {other:?}"),
|
|
}
|
|
|
|
let save = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2SaveSession.as_str().to_owned(),
|
|
params: vec![],
|
|
meta: Default::default(),
|
|
});
|
|
assert_eq!(save.result, Some(RpcValue::String("OK".to_owned())));
|
|
|
|
let loaded = load_session_file(&session_path).expect("saved session file should load");
|
|
let entry = loaded
|
|
.entries
|
|
.iter()
|
|
.find(|entry| entry.gid == gid)
|
|
.expect("saved session should include BT gid");
|
|
let metadata = entry
|
|
.metadata
|
|
.as_ref()
|
|
.expect("saved session entry should include metadata");
|
|
assert_eq!(metadata.get("status"), Some(&"paused".to_owned()));
|
|
assert_eq!(metadata.get("bt.file.0.selected"), Some(&"true".to_owned()));
|
|
|
|
let _ = std::fs::remove_file(session_path);
|
|
let _ = std::fs::remove_dir_all(session_root);
|
|
}
|
|
|
|
#[test]
|
|
fn bt_seeding_and_share_visibility_fields_exist_and_are_type_stable() {
|
|
let torrent_payload = "ZDg6YW5ub3VuY2UzNTpodHRwOi8vdHJhY2tlci5leGFtcGxlLm9yZy9hbm5vdW5jZTQ6aW5mb2Q0Om5hbWUxMDp1YnVudHUuaXNvMTI6cGllY2UgbGVuZ3RoaTE2Mzg0ZTY6bGVuZ3RoaTMyNzY4ZTY6cGllY2VzNDA6YWFhYWFhYWFhYWFhYWFhYWFhYWFiYmJiYmJiYmJiYmJiYmJiYmJiYmVl";
|
|
let mut dispatcher = InProcessRpcDispatcher::new();
|
|
let add = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2AddTorrent.as_str().to_owned(),
|
|
params: vec![RpcValue::String(torrent_payload.to_owned())],
|
|
meta: Default::default(),
|
|
});
|
|
let gid = match add.result {
|
|
Some(RpcValue::String(gid)) => gid,
|
|
other => panic!("unexpected addTorrent result: {other:?}"),
|
|
};
|
|
|
|
let status = dispatcher.dispatch_json(JsonRpcRequest {
|
|
jsonrpc: Some("2.0".to_owned()),
|
|
id: None,
|
|
method: RpcMethod::Aria2TellStatus.as_str().to_owned(),
|
|
params: vec![RpcValue::String(gid)],
|
|
meta: Default::default(),
|
|
});
|
|
match status.result {
|
|
Some(RpcValue::Object(payload)) => {
|
|
assert_eq!(payload.get("isBt"), Some(&RpcValue::Bool(true)));
|
|
assert!(
|
|
matches!(payload.get("status"), Some(RpcValue::String(_)),),
|
|
"status field should remain visible for BT"
|
|
);
|
|
assert!(
|
|
payload.contains_key("seeder"),
|
|
"BT status should expose seeder visibility field"
|
|
);
|
|
assert!(
|
|
payload.contains_key("seeders"),
|
|
"BT status should expose seeders visibility field"
|
|
);
|
|
assert!(
|
|
payload.contains_key("numSeeders"),
|
|
"BT status should expose numSeeders visibility field"
|
|
);
|
|
assert!(
|
|
payload.contains_key("shareRatio"),
|
|
"BT status should expose shareRatio visibility field"
|
|
);
|
|
assert!(
|
|
payload.contains_key("shareRatioProgress"),
|
|
"BT status should expose shareRatioProgress visibility field"
|
|
);
|
|
assert!(
|
|
payload.contains_key("shareRatioRemaining"),
|
|
"BT status should expose shareRatioRemaining visibility field"
|
|
);
|
|
assert!(
|
|
payload.contains_key("shareTime"),
|
|
"BT status should expose shareTime visibility field"
|
|
);
|
|
if let Some(value) = payload.get("shareRatio") {
|
|
assert!(
|
|
matches!(value, RpcValue::String(_) | RpcValue::Number(_)),
|
|
"shareRatio should stay scalar"
|
|
);
|
|
}
|
|
if let Some(value) = payload.get("shareTime") {
|
|
assert!(
|
|
matches!(value, RpcValue::String(_) | RpcValue::Number(_)),
|
|
"shareTime should stay scalar"
|
|
);
|
|
}
|
|
}
|
|
other => panic!("unexpected tellStatus payload for BT share visibility: {other:?}"),
|
|
}
|
|
}
|