169 lines
6.6 KiB
Rust
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"
|
|
);
|
|
}
|