Skip to content

Commit c785fd1

Browse files
committed
refactor: [torrust#1211] move tests to AnnounceHandler
1 parent c3f0bc7 commit c785fd1

File tree

2 files changed

+388
-303
lines changed

2 files changed

+388
-303
lines changed

src/core/announce_handler.rs

+388-1
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,397 @@ impl From<i32> for PeersWanted {
155155
}
156156

157157
#[must_use]
158-
pub fn assign_ip_address_to_peer(remote_client_ip: &IpAddr, tracker_external_ip: Option<IpAddr>) -> IpAddr {
158+
fn assign_ip_address_to_peer(remote_client_ip: &IpAddr, tracker_external_ip: Option<IpAddr>) -> IpAddr {
159159
if let Some(host_ip) = tracker_external_ip.filter(|_| remote_client_ip.is_loopback()) {
160160
host_ip
161161
} else {
162162
*remote_client_ip
163163
}
164164
}
165+
166+
#[cfg(test)]
167+
mod tests {
168+
// Integration tests for the core module.
169+
170+
mod the_announce_handler {
171+
172+
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
173+
use std::str::FromStr;
174+
use std::sync::Arc;
175+
176+
use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId};
177+
use bittorrent_primitives::info_hash::InfoHash;
178+
use torrust_tracker_primitives::peer::Peer;
179+
use torrust_tracker_primitives::DurationSinceUnixEpoch;
180+
use torrust_tracker_test_helpers::configuration;
181+
182+
use crate::app_test::initialize_tracker_dependencies;
183+
use crate::core::announce_handler::AnnounceHandler;
184+
use crate::core::scrape_handler::ScrapeHandler;
185+
use crate::core::torrent::manager::TorrentsManager;
186+
use crate::core::torrent::repository::in_memory::InMemoryTorrentRepository;
187+
188+
fn public_tracker() -> (Arc<AnnounceHandler>, Arc<InMemoryTorrentRepository>, Arc<ScrapeHandler>) {
189+
let config = configuration::ephemeral_public();
190+
191+
let (
192+
_database,
193+
_in_memory_whitelist,
194+
whitelist_authorization,
195+
_authentication_service,
196+
in_memory_torrent_repository,
197+
db_torrent_repository,
198+
_torrents_manager,
199+
) = initialize_tracker_dependencies(&config);
200+
201+
let announce_handler = Arc::new(AnnounceHandler::new(
202+
&config.core,
203+
&in_memory_torrent_repository,
204+
&db_torrent_repository,
205+
));
206+
207+
let scrape_handler = Arc::new(ScrapeHandler::new(&whitelist_authorization, &in_memory_torrent_repository));
208+
209+
(announce_handler, in_memory_torrent_repository, scrape_handler)
210+
}
211+
212+
pub fn tracker_persisting_torrents_in_database(
213+
) -> (Arc<AnnounceHandler>, Arc<TorrentsManager>, Arc<InMemoryTorrentRepository>) {
214+
let mut config = configuration::ephemeral_listed();
215+
config.core.tracker_policy.persistent_torrent_completed_stat = true;
216+
217+
let (
218+
_database,
219+
_in_memory_whitelist,
220+
_whitelist_authorization,
221+
_authentication_service,
222+
in_memory_torrent_repository,
223+
db_torrent_repository,
224+
torrents_manager,
225+
) = initialize_tracker_dependencies(&config);
226+
227+
let announce_handler = Arc::new(AnnounceHandler::new(
228+
&config.core,
229+
&in_memory_torrent_repository,
230+
&db_torrent_repository,
231+
));
232+
233+
(announce_handler, torrents_manager, in_memory_torrent_repository)
234+
}
235+
236+
fn sample_info_hash() -> InfoHash {
237+
"3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::<InfoHash>().unwrap()
238+
}
239+
240+
// The client peer IP
241+
fn peer_ip() -> IpAddr {
242+
IpAddr::V4(Ipv4Addr::from_str("126.0.0.1").unwrap())
243+
}
244+
245+
/// Sample peer whose state is not relevant for the tests
246+
fn sample_peer() -> Peer {
247+
complete_peer()
248+
}
249+
250+
/// Sample peer when for tests that need more than one peer
251+
fn sample_peer_1() -> Peer {
252+
Peer {
253+
peer_id: PeerId(*b"-qB00000000000000001"),
254+
peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8081),
255+
updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
256+
uploaded: NumberOfBytes::new(0),
257+
downloaded: NumberOfBytes::new(0),
258+
left: NumberOfBytes::new(0),
259+
event: AnnounceEvent::Completed,
260+
}
261+
}
262+
263+
/// Sample peer when for tests that need more than one peer
264+
fn sample_peer_2() -> Peer {
265+
Peer {
266+
peer_id: PeerId(*b"-qB00000000000000002"),
267+
peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 2)), 8082),
268+
updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
269+
uploaded: NumberOfBytes::new(0),
270+
downloaded: NumberOfBytes::new(0),
271+
left: NumberOfBytes::new(0),
272+
event: AnnounceEvent::Completed,
273+
}
274+
}
275+
276+
fn seeder() -> Peer {
277+
complete_peer()
278+
}
279+
280+
fn leecher() -> Peer {
281+
incomplete_peer()
282+
}
283+
284+
fn started_peer() -> Peer {
285+
incomplete_peer()
286+
}
287+
288+
fn completed_peer() -> Peer {
289+
complete_peer()
290+
}
291+
292+
/// A peer that counts as `complete` is swarm metadata
293+
/// IMPORTANT!: it only counts if the it has been announce at least once before
294+
/// announcing the `AnnounceEvent::Completed` event.
295+
fn complete_peer() -> Peer {
296+
Peer {
297+
peer_id: PeerId(*b"-qB00000000000000000"),
298+
peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080),
299+
updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
300+
uploaded: NumberOfBytes::new(0),
301+
downloaded: NumberOfBytes::new(0),
302+
left: NumberOfBytes::new(0), // No bytes left to download
303+
event: AnnounceEvent::Completed,
304+
}
305+
}
306+
307+
/// A peer that counts as `incomplete` is swarm metadata
308+
fn incomplete_peer() -> Peer {
309+
Peer {
310+
peer_id: PeerId(*b"-qB00000000000000000"),
311+
peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080),
312+
updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
313+
uploaded: NumberOfBytes::new(0),
314+
downloaded: NumberOfBytes::new(0),
315+
left: NumberOfBytes::new(1000), // Still bytes to download
316+
event: AnnounceEvent::Started,
317+
}
318+
}
319+
320+
mod for_all_tracker_config_modes {
321+
322+
mod handling_an_announce_request {
323+
324+
use std::sync::Arc;
325+
326+
use crate::core::announce_handler::tests::the_announce_handler::{
327+
peer_ip, public_tracker, sample_info_hash, sample_peer, sample_peer_1, sample_peer_2,
328+
};
329+
use crate::core::announce_handler::PeersWanted;
330+
331+
mod should_assign_the_ip_to_the_peer {
332+
333+
use std::net::{IpAddr, Ipv4Addr};
334+
335+
use crate::core::announce_handler::assign_ip_address_to_peer;
336+
337+
#[test]
338+
fn using_the_source_ip_instead_of_the_ip_in_the_announce_request() {
339+
let remote_ip = IpAddr::V4(Ipv4Addr::new(126, 0, 0, 2));
340+
341+
let peer_ip = assign_ip_address_to_peer(&remote_ip, None);
342+
343+
assert_eq!(peer_ip, remote_ip);
344+
}
345+
346+
mod and_when_the_client_ip_is_a_ipv4_loopback_ip {
347+
348+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
349+
use std::str::FromStr;
350+
351+
use crate::core::announce_handler::assign_ip_address_to_peer;
352+
353+
#[test]
354+
fn it_should_use_the_loopback_ip_if_the_tracker_does_not_have_the_external_ip_configuration() {
355+
let remote_ip = IpAddr::V4(Ipv4Addr::LOCALHOST);
356+
357+
let peer_ip = assign_ip_address_to_peer(&remote_ip, None);
358+
359+
assert_eq!(peer_ip, remote_ip);
360+
}
361+
362+
#[test]
363+
fn it_should_use_the_external_tracker_ip_in_tracker_configuration_if_it_is_defined() {
364+
let remote_ip = IpAddr::V4(Ipv4Addr::LOCALHOST);
365+
366+
let tracker_external_ip = IpAddr::V4(Ipv4Addr::from_str("126.0.0.1").unwrap());
367+
368+
let peer_ip = assign_ip_address_to_peer(&remote_ip, Some(tracker_external_ip));
369+
370+
assert_eq!(peer_ip, tracker_external_ip);
371+
}
372+
373+
#[test]
374+
fn it_should_use_the_external_ip_in_the_tracker_configuration_if_it_is_defined_even_if_the_external_ip_is_an_ipv6_ip(
375+
) {
376+
let remote_ip = IpAddr::V4(Ipv4Addr::LOCALHOST);
377+
378+
let tracker_external_ip =
379+
IpAddr::V6(Ipv6Addr::from_str("2345:0425:2CA1:0000:0000:0567:5673:23b5").unwrap());
380+
381+
let peer_ip = assign_ip_address_to_peer(&remote_ip, Some(tracker_external_ip));
382+
383+
assert_eq!(peer_ip, tracker_external_ip);
384+
}
385+
}
386+
387+
mod and_when_client_ip_is_a_ipv6_loopback_ip {
388+
389+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
390+
use std::str::FromStr;
391+
392+
use crate::core::announce_handler::assign_ip_address_to_peer;
393+
394+
#[test]
395+
fn it_should_use_the_loopback_ip_if_the_tracker_does_not_have_the_external_ip_configuration() {
396+
let remote_ip = IpAddr::V6(Ipv6Addr::LOCALHOST);
397+
398+
let peer_ip = assign_ip_address_to_peer(&remote_ip, None);
399+
400+
assert_eq!(peer_ip, remote_ip);
401+
}
402+
403+
#[test]
404+
fn it_should_use_the_external_ip_in_tracker_configuration_if_it_is_defined() {
405+
let remote_ip = IpAddr::V6(Ipv6Addr::LOCALHOST);
406+
407+
let tracker_external_ip =
408+
IpAddr::V6(Ipv6Addr::from_str("2345:0425:2CA1:0000:0000:0567:5673:23b5").unwrap());
409+
410+
let peer_ip = assign_ip_address_to_peer(&remote_ip, Some(tracker_external_ip));
411+
412+
assert_eq!(peer_ip, tracker_external_ip);
413+
}
414+
415+
#[test]
416+
fn it_should_use_the_external_ip_in_the_tracker_configuration_if_it_is_defined_even_if_the_external_ip_is_an_ipv4_ip(
417+
) {
418+
let remote_ip = IpAddr::V6(Ipv6Addr::LOCALHOST);
419+
420+
let tracker_external_ip = IpAddr::V4(Ipv4Addr::from_str("126.0.0.1").unwrap());
421+
422+
let peer_ip = assign_ip_address_to_peer(&remote_ip, Some(tracker_external_ip));
423+
424+
assert_eq!(peer_ip, tracker_external_ip);
425+
}
426+
}
427+
}
428+
429+
#[tokio::test]
430+
async fn it_should_return_the_announce_data_with_an_empty_peer_list_when_it_is_the_first_announced_peer() {
431+
let (announce_handler, _in_memory_torrent_repository, _scrape_handler) = public_tracker();
432+
433+
let mut peer = sample_peer();
434+
435+
let announce_data = announce_handler.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
436+
437+
assert_eq!(announce_data.peers, vec![]);
438+
}
439+
440+
#[tokio::test]
441+
async fn it_should_return_the_announce_data_with_the_previously_announced_peers() {
442+
let (announce_handler, _in_memory_torrent_repository, _scrape_handler) = public_tracker();
443+
444+
let mut previously_announced_peer = sample_peer_1();
445+
announce_handler.announce(
446+
&sample_info_hash(),
447+
&mut previously_announced_peer,
448+
&peer_ip(),
449+
&PeersWanted::All,
450+
);
451+
452+
let mut peer = sample_peer_2();
453+
let announce_data = announce_handler.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
454+
455+
assert_eq!(announce_data.peers, vec![Arc::new(previously_announced_peer)]);
456+
}
457+
458+
mod it_should_update_the_swarm_stats_for_the_torrent {
459+
460+
use crate::core::announce_handler::tests::the_announce_handler::{
461+
completed_peer, leecher, peer_ip, public_tracker, sample_info_hash, seeder, started_peer,
462+
};
463+
use crate::core::announce_handler::PeersWanted;
464+
465+
#[tokio::test]
466+
async fn when_the_peer_is_a_seeder() {
467+
let (announce_handler, _in_memory_torrent_repository, _scrape_handler) = public_tracker();
468+
469+
let mut peer = seeder();
470+
471+
let announce_data =
472+
announce_handler.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
473+
474+
assert_eq!(announce_data.stats.complete, 1);
475+
}
476+
477+
#[tokio::test]
478+
async fn when_the_peer_is_a_leecher() {
479+
let (announce_handler, _in_memory_torrent_repository, _scrape_handler) = public_tracker();
480+
481+
let mut peer = leecher();
482+
483+
let announce_data =
484+
announce_handler.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
485+
486+
assert_eq!(announce_data.stats.incomplete, 1);
487+
}
488+
489+
#[tokio::test]
490+
async fn when_a_previously_announced_started_peer_has_completed_downloading() {
491+
let (announce_handler, _in_memory_torrent_repository, _scrape_handler) = public_tracker();
492+
493+
// We have to announce with "started" event because peer does not count if peer was not previously known
494+
let mut started_peer = started_peer();
495+
announce_handler.announce(&sample_info_hash(), &mut started_peer, &peer_ip(), &PeersWanted::All);
496+
497+
let mut completed_peer = completed_peer();
498+
let announce_data =
499+
announce_handler.announce(&sample_info_hash(), &mut completed_peer, &peer_ip(), &PeersWanted::All);
500+
501+
assert_eq!(announce_data.stats.downloaded, 1);
502+
}
503+
}
504+
}
505+
}
506+
507+
mod handling_torrent_persistence {
508+
509+
use aquatic_udp_protocol::AnnounceEvent;
510+
use torrust_tracker_torrent_repository::entry::EntrySync;
511+
512+
use crate::core::announce_handler::tests::the_announce_handler::{
513+
peer_ip, sample_info_hash, sample_peer, tracker_persisting_torrents_in_database,
514+
};
515+
use crate::core::announce_handler::PeersWanted;
516+
517+
#[tokio::test]
518+
async fn it_should_persist_the_number_of_completed_peers_for_all_torrents_into_the_database() {
519+
let (announce_handler, torrents_manager, in_memory_torrent_repository) =
520+
tracker_persisting_torrents_in_database();
521+
522+
let info_hash = sample_info_hash();
523+
524+
let mut peer = sample_peer();
525+
526+
peer.event = AnnounceEvent::Started;
527+
let announce_data = announce_handler.announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::All);
528+
assert_eq!(announce_data.stats.downloaded, 0);
529+
530+
peer.event = AnnounceEvent::Completed;
531+
let announce_data = announce_handler.announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::All);
532+
assert_eq!(announce_data.stats.downloaded, 1);
533+
534+
// Remove the newly updated torrent from memory
535+
let _unused = in_memory_torrent_repository.remove(&info_hash);
536+
537+
torrents_manager.load_torrents_from_database().unwrap();
538+
539+
let torrent_entry = in_memory_torrent_repository
540+
.get(&info_hash)
541+
.expect("it should be able to get entry");
542+
543+
// It persists the number of completed peers.
544+
assert_eq!(torrent_entry.get_swarm_metadata().downloaded, 1);
545+
546+
// It does not persist the peers
547+
assert!(torrent_entry.peers_is_empty());
548+
}
549+
}
550+
}
551+
}

0 commit comments

Comments
 (0)