@@ -98,3 +98,272 @@ pub enum TorrentError {
9898 CouldNotSendResponse ,
9999 InvalidInfoHash ,
100100}
101+
102+ #[ cfg( test) ]
103+ mod tests {
104+ use std:: {
105+ net:: { IpAddr , Ipv4Addr , SocketAddr } ,
106+ ops:: Sub ,
107+ time:: Duration ,
108+ } ;
109+
110+ use aquatic_udp_protocol:: { AnnounceEvent , NumberOfBytes } ;
111+
112+ use crate :: {
113+ peer:: TorrentPeer ,
114+ protocol:: clock:: { DefaultClock , DurationSinceUnixEpoch , StoppedClock , StoppedTime , Time , WorkingClock } ,
115+ torrent:: TorrentEntry ,
116+ PeerId ,
117+ } ;
118+
119+ struct TorrentPeerBuilder {
120+ peer : TorrentPeer ,
121+ }
122+
123+ impl TorrentPeerBuilder {
124+ pub fn default ( ) -> TorrentPeerBuilder {
125+ let default_peer = TorrentPeer {
126+ peer_id : PeerId ( [ 0u8 ; 20 ] ) ,
127+ peer_addr : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) ) , 8080 ) ,
128+ updated : DefaultClock :: now ( ) ,
129+ uploaded : NumberOfBytes ( 0 ) ,
130+ downloaded : NumberOfBytes ( 0 ) ,
131+ left : NumberOfBytes ( 0 ) ,
132+ event : AnnounceEvent :: Started ,
133+ } ;
134+ TorrentPeerBuilder { peer : default_peer }
135+ }
136+
137+ pub fn with_event_completed ( mut self ) -> Self {
138+ self . peer . event = AnnounceEvent :: Completed ;
139+ self
140+ }
141+
142+ pub fn with_peer_address ( mut self , peer_addr : SocketAddr ) -> Self {
143+ self . peer . peer_addr = peer_addr;
144+ self
145+ }
146+
147+ pub fn with_peer_id ( mut self , peer_id : PeerId ) -> Self {
148+ self . peer . peer_id = peer_id;
149+ self
150+ }
151+
152+ pub fn with_number_of_bytes_left ( mut self , left : i64 ) -> Self {
153+ self . peer . left = NumberOfBytes ( left) ;
154+ self
155+ }
156+
157+ pub fn updated_at ( mut self , updated : DurationSinceUnixEpoch ) -> Self {
158+ self . peer . updated = updated;
159+ self
160+ }
161+
162+ pub fn into ( self ) -> TorrentPeer {
163+ self . peer
164+ }
165+ }
166+
167+ /// A torrent seeder is a peer with 0 bytes left to download which
168+ /// has not announced it has stopped
169+ fn a_torrent_seeder ( ) -> TorrentPeer {
170+ TorrentPeerBuilder :: default ( )
171+ . with_number_of_bytes_left ( 0 )
172+ . with_event_completed ( )
173+ . into ( )
174+ }
175+
176+ /// A torrent leecher is a peer that is not a seeder.
177+ /// Leecher: left > 0 OR event = Stopped
178+ fn a_torrent_leecher ( ) -> TorrentPeer {
179+ TorrentPeerBuilder :: default ( )
180+ . with_number_of_bytes_left ( 1 )
181+ . with_event_completed ( )
182+ . into ( )
183+ }
184+
185+ #[ test]
186+ fn the_default_torrent_entry_should_contain_an_empty_list_of_peers ( ) {
187+ let torrent_entry = TorrentEntry :: new ( ) ;
188+
189+ assert_eq ! ( torrent_entry. get_peers( None ) . len( ) , 0 ) ;
190+ }
191+
192+ #[ test]
193+ fn a_new_peer_can_be_added_to_a_torrent_entry ( ) {
194+ let mut torrent_entry = TorrentEntry :: new ( ) ;
195+ let torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
196+
197+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
198+
199+ assert_eq ! ( * torrent_entry. get_peers( None ) [ 0 ] , torrent_peer) ;
200+ assert_eq ! ( torrent_entry. get_peers( None ) . len( ) , 1 ) ;
201+ }
202+
203+ #[ test]
204+ fn a_torrent_entry_should_contain_the_list_of_peers_that_were_added_to_the_torrent ( ) {
205+ let mut torrent_entry = TorrentEntry :: new ( ) ;
206+ let torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
207+
208+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
209+
210+ assert_eq ! ( torrent_entry. get_peers( None ) , vec![ & torrent_peer] ) ;
211+ }
212+
213+ #[ test]
214+ fn a_peer_can_be_updated_in_a_torrent_entry ( ) {
215+ let mut torrent_entry = TorrentEntry :: new ( ) ;
216+ let mut torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
217+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
218+
219+ torrent_peer. event = AnnounceEvent :: Completed ; // Update the peer
220+ torrent_entry. update_peer ( & torrent_peer) ; // Update the peer in the torrent entry
221+
222+ assert_eq ! ( torrent_entry. get_peers( None ) [ 0 ] . event, AnnounceEvent :: Completed ) ;
223+ }
224+
225+ #[ test]
226+ fn a_peer_should_be_removed_from_a_torrent_entry_when_the_peer_announces_it_has_stopped ( ) {
227+ let mut torrent_entry = TorrentEntry :: new ( ) ;
228+ let mut torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
229+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
230+
231+ torrent_peer. event = AnnounceEvent :: Stopped ; // Update the peer
232+ torrent_entry. update_peer ( & torrent_peer) ; // Update the peer in the torrent entry
233+
234+ assert_eq ! ( torrent_entry. get_peers( None ) . len( ) , 0 ) ;
235+ }
236+
237+ #[ test]
238+ fn torrent_stats_change_when_a_previously_known_peer_announces_it_has_completed_the_torrent ( ) {
239+ let mut torrent_entry = TorrentEntry :: new ( ) ;
240+ let mut torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
241+
242+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
243+
244+ torrent_peer. event = AnnounceEvent :: Completed ; // Update the peer
245+ let stats_have_changed = torrent_entry. update_peer ( & torrent_peer) ; // Update the peer in the torrent entry
246+
247+ assert ! ( stats_have_changed) ;
248+ }
249+
250+ #[ test]
251+ fn torrent_stats_should_not_change_when_a_peer_announces_it_has_completed_the_torrent_if_it_is_the_first_announce_from_the_peer (
252+ ) {
253+ let mut torrent_entry = TorrentEntry :: new ( ) ;
254+ let torrent_peer_announcing_complete_event = TorrentPeerBuilder :: default ( ) . with_event_completed ( ) . into ( ) ;
255+
256+ // Add a peer that did not exist before in the entry
257+ let torrent_stats_have_not_changed = !torrent_entry. update_peer ( & torrent_peer_announcing_complete_event) ;
258+
259+ assert ! ( torrent_stats_have_not_changed) ;
260+ }
261+
262+ #[ test]
263+ fn a_torrent_entry_could_filter_out_peers_with_a_given_socket_address ( ) {
264+ let mut torrent_entry = TorrentEntry :: new ( ) ;
265+ let peer_socket_address = SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) ) , 8080 ) ;
266+ let torrent_peer = TorrentPeerBuilder :: default ( ) . with_peer_address ( peer_socket_address) . into ( ) ;
267+ torrent_entry. update_peer ( & torrent_peer) ; // Add peer
268+
269+ // Get peers excluding the one we have just added
270+ let peers = torrent_entry. get_peers ( Some ( & peer_socket_address) ) ;
271+
272+ assert_eq ! ( peers. len( ) , 0 ) ;
273+ }
274+
275+ fn peer_id_from_i32 ( number : i32 ) -> PeerId {
276+ let peer_id = number. to_le_bytes ( ) ;
277+ PeerId ( [
278+ 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , peer_id[ 0 ] , peer_id[ 1 ] , peer_id[ 2 ] ,
279+ peer_id[ 3 ] ,
280+ ] )
281+ }
282+
283+ #[ test]
284+ fn the_tracker_should_limit_the_list_of_peers_to_74_when_clients_scrape_torrents ( ) {
285+ let mut torrent_entry = TorrentEntry :: new ( ) ;
286+
287+ // We add one more peer than the scrape limit
288+ for peer_number in 1 ..=74 + 1 {
289+ let torrent_peer = TorrentPeerBuilder :: default ( )
290+ . with_peer_id ( peer_id_from_i32 ( peer_number) )
291+ . into ( ) ;
292+ torrent_entry. update_peer ( & torrent_peer) ;
293+ }
294+
295+ let peers = torrent_entry. get_peers ( None ) ;
296+
297+ assert_eq ! ( peers. len( ) , 74 )
298+ }
299+
300+ #[ test]
301+ fn torrent_stats_should_have_the_number_of_seeders_for_a_torrent ( ) {
302+ let mut torrent_entry = TorrentEntry :: new ( ) ;
303+ let torrent_seeder = a_torrent_seeder ( ) ;
304+
305+ torrent_entry. update_peer ( & torrent_seeder) ; // Add seeder
306+
307+ assert_eq ! ( torrent_entry. get_stats( ) . 0 , 1 ) ;
308+ }
309+
310+ #[ test]
311+ fn torrent_stats_should_have_the_number_of_leechers_for_a_torrent ( ) {
312+ let mut torrent_entry = TorrentEntry :: new ( ) ;
313+ let torrent_leecher = a_torrent_leecher ( ) ;
314+
315+ torrent_entry. update_peer ( & torrent_leecher) ; // Add leecher
316+
317+ assert_eq ! ( torrent_entry. get_stats( ) . 2 , 1 ) ;
318+ }
319+
320+ #[ test]
321+ fn torrent_stats_should_have_the_number_of_peers_that_having_announced_at_least_two_events_the_latest_one_is_the_completed_event (
322+ ) {
323+ let mut torrent_entry = TorrentEntry :: new ( ) ;
324+ let mut torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
325+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
326+
327+ // Announce "Completed" torrent download event.
328+ torrent_peer. event = AnnounceEvent :: Completed ;
329+ torrent_entry. update_peer ( & torrent_peer) ; // Update the peer
330+
331+ let number_of_previously_known_peers_with_completed_torrent = torrent_entry. get_stats ( ) . 1 ;
332+
333+ assert_eq ! ( number_of_previously_known_peers_with_completed_torrent, 1 ) ;
334+ }
335+
336+ #[ test]
337+ fn torrent_stats_should_not_include_a_peer_in_the_completed_counter_if_the_peer_has_announced_only_one_event ( ) {
338+ let mut torrent_entry = TorrentEntry :: new ( ) ;
339+ let torrent_peer_announcing_complete_event = TorrentPeerBuilder :: default ( ) . with_event_completed ( ) . into ( ) ;
340+
341+ // Announce "Completed" torrent download event.
342+ // It's the first event announced from this peer.
343+ torrent_entry. update_peer ( & torrent_peer_announcing_complete_event) ; // Add the peer
344+
345+ let number_of_peers_with_completed_torrent = torrent_entry. get_stats ( ) . 1 ;
346+
347+ assert_eq ! ( number_of_peers_with_completed_torrent, 0 ) ;
348+ }
349+
350+ #[ test]
351+ fn a_torrent_entry_should_remove_a_peer_not_updated_after_a_timeout_in_seconds ( ) {
352+ let mut torrent_entry = TorrentEntry :: new ( ) ;
353+
354+ let timeout = 120u32 ;
355+
356+ let now = WorkingClock :: now ( ) ;
357+ StoppedClock :: local_set ( & now) ;
358+
359+ let timeout_seconds_before_now = now. sub ( Duration :: from_secs ( timeout as u64 ) ) ;
360+ let inactive_peer = TorrentPeerBuilder :: default ( )
361+ . updated_at ( timeout_seconds_before_now. sub ( Duration :: from_secs ( 1 ) ) )
362+ . into ( ) ;
363+ torrent_entry. update_peer ( & inactive_peer) ; // Add the peer
364+
365+ torrent_entry. remove_inactive_peers ( timeout) ;
366+
367+ assert_eq ! ( torrent_entry. peers. len( ) , 0 ) ;
368+ }
369+ }
0 commit comments