Files

169 lines
6.6 KiB
Rust

#![expect(
missing_docs,
reason = "integration test scenarios are documented by case names rather than item-level rustdoc"
)]
use std::collections::BTreeMap;
use aria2_rust_pro_cli as _;
use aria2_rust_pro_compat as _;
use aria2_rust_pro_core as _;
use aria2_rust_pro_protocol::{
DhtMessageModel, DhtNodeModel,
torrent::{PeerWireExtensionHandshakeModel, PeerWireMessageKind, PeerWireMetadataMessageModel},
};
use aria2_rust_pro_rpc::{InProcessRpcDispatcher, RpcMethod, RpcValue};
use aria2_rust_pro_storage as _;
use aria2_rust_pro_tests as _;
use criterion as _;
#[path = "../test_support/support.rs"]
mod support;
fn tell_status(dispatcher: &mut InProcessRpcDispatcher, gid: &str) -> BTreeMap<String, RpcValue> {
support::rpc_result_object(dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2TellStatus,
vec![RpcValue::String(gid.to_owned())],
)))
}
fn get_files(dispatcher: &mut InProcessRpcDispatcher, gid: &str) -> Vec<RpcValue> {
support::rpc_result_array(dispatcher.dispatch_json(support::rpc_request(
RpcMethod::Aria2GetFiles,
vec![RpcValue::String(gid.to_owned())],
)))
}
fn first_file<'a>(files: &'a [RpcValue], context: &str) -> &'a BTreeMap<String, RpcValue> {
match files.first() {
Some(RpcValue::Object(file)) => file,
other => panic!("unexpected first file payload for {context}: {other:?}"),
}
}
fn seed_peer_and_promote_metadata(
dispatcher: &mut InProcessRpcDispatcher,
gid: &str,
info_hash_hex: &str,
) {
dispatcher
.apply_dht_get_peers_result(
gid,
&DhtNodeModel {
node_id: String::new(),
address: "203.0.113.77".to_owned(),
port: 51413,
},
&DhtMessageModel::get_peers_response(
b"gp".to_vec(),
vec![0x55; 20],
Some(b"dht-token".to_vec()),
None,
vec![support::compact_peer([198, 51, 100, 42], 51415)],
),
)
.expect("dht get_peers should seed a connectable peer");
let connector = support::FakePeerWireConnector::new(support::peer_wire_handshake_and_frames(
support::decode_hex_20(info_hash_hex),
*b"-AZ2060-META-PROMO01",
&[
PeerWireMessageKind::Unchoke,
PeerWireMessageKind::Extension(
PeerWireExtensionHandshakeModel {
extensions: BTreeMap::from([("ut_metadata".to_owned(), 3_u8)]),
client_name: Some("libtorrent/2.0.11".to_owned()),
metadata_size: Some(
u32::try_from(support::BT_TORRENT_FIXTURE_BYTES.len())
.expect("fixture metadata length should fit u32"),
),
request_queue: Some(64),
}
.to_peer_wire_message(),
),
PeerWireMessageKind::Extension(
PeerWireMetadataMessageModel::data(
0,
u32::try_from(support::BT_TORRENT_FIXTURE_BYTES.len())
.expect("fixture metadata length should fit u32"),
support::BT_TORRENT_FIXTURE_BYTES.to_vec(),
)
.to_peer_wire_message(3),
),
],
));
dispatcher
.execute_peer_wire_exchange(gid, &connector)
.expect("peer-wire metadata exchange should promote the magnet session");
}
#[test]
fn magnet_piece_observation_clears_metadata_only_and_pending_snapshot_flags() {
let mut dispatcher = InProcessRpcDispatcher::new();
let reference_gid = support::add_torrent(&mut dispatcher);
let reference_status = tell_status(&mut dispatcher, &reference_gid);
let magnet_uri = support::rpc_string_field(&reference_status, "magnetUri");
let info_hash = support::rpc_string_field(&reference_status, "infoHash");
let gid = support::add_magnet(&mut dispatcher, &magnet_uri);
let before = tell_status(&mut dispatcher, &gid);
assert!(support::rpc_bool_field(&before, "metadataOnly"));
assert_eq!(support::rpc_string_field(&before, "magnetUri"), magnet_uri);
let before_snapshot = dispatcher
.bt_runtime_coordinator_snapshot(&gid)
.expect("snapshot should inspect metadata-only magnet state");
assert!(before_snapshot.metadata_only);
assert!(before_snapshot.metadata_exchange_pending);
seed_peer_and_promote_metadata(&mut dispatcher, &gid, &info_hash);
let after = tell_status(&mut dispatcher, &gid);
assert!(!support::rpc_bool_field(&after, "metadataOnly"));
assert_eq!(support::rpc_string_field(&after, "magnetUri"), magnet_uri);
let after_snapshot = dispatcher
.bt_runtime_coordinator_snapshot(&gid)
.expect("snapshot should inspect promoted magnet state");
assert!(!after_snapshot.metadata_only);
assert!(!after_snapshot.metadata_exchange_pending);
}
#[test]
fn promoted_magnet_session_should_match_reference_torrent_file_surface() {
let mut dispatcher = InProcessRpcDispatcher::new();
let reference_gid = support::add_torrent(&mut dispatcher);
let reference_status = tell_status(&mut dispatcher, &reference_gid);
let reference_files = get_files(&mut dispatcher, &reference_gid);
let reference_file = first_file(&reference_files, "reference torrent");
let magnet_uri = support::rpc_string_field(&reference_status, "magnetUri");
let info_hash = support::rpc_string_field(&reference_status, "infoHash");
let gid = support::add_magnet(&mut dispatcher, &magnet_uri);
seed_peer_and_promote_metadata(&mut dispatcher, &gid, &info_hash);
let promoted_status = tell_status(&mut dispatcher, &gid);
assert!(
!support::rpc_bool_field(&promoted_status, "metadataOnly"),
"metadata promotion should leave metadata-only mode before comparing the public torrent surface"
);
assert_eq!(
support::rpc_u64_field(&promoted_status, "totalLength"),
support::rpc_u64_field(&reference_status, "totalLength"),
"promoted magnet sessions should hydrate the same totalLength visible on the equivalent .torrent bootstrap"
);
let promoted_files = get_files(&mut dispatcher, &gid);
let promoted_file = first_file(&promoted_files, "promoted magnet");
assert_eq!(
promoted_file.get("path"),
reference_file.get("path"),
"promoted magnet sessions should expose the real torrent file path instead of a synthetic placeholder row"
);
assert_eq!(
promoted_file.get("length"),
reference_file.get("length"),
"promoted magnet sessions should expose the same per-file length as the equivalent .torrent bootstrap"
);
}