chore: initial sanitized public snapshot

This commit is contained in:
Aria2 Rust Pro Contributors
2026-07-18 14:16:57 +08:00
commit 17688c3e34
321 changed files with 76859 additions and 0 deletions
@@ -0,0 +1,701 @@
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:?}"),
}
}