chore: initial sanitized public snapshot
This commit is contained in:
@@ -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:?}"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user