Skip to content

Commit 92e844c

Browse files
committed
Merge #286: Fix different info-hashes for uploaded and downloaded torrents
d7cc040 refactor: [#282] tests (Jose Celano) 6ff3600 fix: [#282] downloaded torrent info-hash matches uploaded one (Jose Celano) b34c7d8 test: [#282] compare uploaded and downloaded torrent info-hashes (Jose Celano) Pull request description: If you upload a torrent and you download it, the downloaded torrent info-hash is not the same if the origin one didn't contain the `private` field in the `info` dictionary. The Index always includes that field in the response because it always stores a `TRUE` or `FALSE` value in the database, even when the field is not in the original torrent file. - [x] Fix the test. It was passing with a different info-hash for no reason. It should fail with the current implementation. - [x] Change the handler to set `private` to `NULL` when not in the original torrent. And do not include it in the response when it's `NULL` in the database. - [x] Refactor all tests using the `e2e::web::api::v1::contexts::torrent::asserts::expected_torrent` function to compare the info-hashes. ACKs for top commit: josecelano: ACK d7cc040 Tree-SHA512: 126e628326430b0d36ef84adaded7fcbee7cb31b70ee866a42164ebefa111c55dc7b43638a4d3b59f4bc84fe20aba19ac4137acc5f03fe7269bd0f8728f1ea61
2 parents b73e19e + d7cc040 commit 92e844c

File tree

10 files changed

+118
-64
lines changed

10 files changed

+118
-64
lines changed

src/databases/mysql.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,6 @@ impl Database for Mysql {
442442
(root_hash.to_string(), true)
443443
};
444444

445-
let private = torrent.info.private.unwrap_or(0);
446-
447445
// add torrent
448446
let torrent_id = query("INSERT INTO torrust_torrents (uploader_id, category_id, info_hash, size, name, pieces, piece_length, private, root_hash, `source`, date_uploaded) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())")
449447
.bind(uploader_id)
@@ -453,7 +451,7 @@ impl Database for Mysql {
453451
.bind(torrent.info.name.to_string())
454452
.bind(pieces)
455453
.bind(torrent.info.piece_length)
456-
.bind(private)
454+
.bind(torrent.info.private)
457455
.bind(root_hash)
458456
.bind(torrent.info.source.clone())
459457
.execute(&mut tx)

src/databases/sqlite.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,6 @@ impl Database for Sqlite {
432432
(root_hash.to_string(), true)
433433
};
434434

435-
let private = torrent.info.private.unwrap_or(0);
436-
437435
// add torrent
438436
let torrent_id = query("INSERT INTO torrust_torrents (uploader_id, category_id, info_hash, size, name, pieces, piece_length, private, root_hash, `source`, date_uploaded) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%S',DATETIME('now', 'utc')))")
439437
.bind(uploader_id)
@@ -443,7 +441,7 @@ impl Database for Sqlite {
443441
.bind(torrent.info.name.to_string())
444442
.bind(pieces)
445443
.bind(torrent.info.piece_length)
446-
.bind(private)
444+
.bind(torrent.info.private)
447445
.bind(root_hash)
448446
.bind(torrent.info.source.clone())
449447
.execute(&mut tx)

src/models/torrent_file.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,6 @@ impl Torrent {
113113
/// This function will panic if the `torrent_info.pieces` is not a valid hex string.
114114
#[must_use]
115115
pub fn from_new_torrent_info_request(torrent_info: NewTorrentInfoRequest) -> Self {
116-
let private = u8::try_from(torrent_info.private.unwrap_or(0)).ok();
117-
118116
// the info part of the torrent file
119117
let mut info = TorrentInfo {
120118
name: torrent_info.name.to_string(),
@@ -123,7 +121,7 @@ impl Torrent {
123121
md5sum: None,
124122
length: None,
125123
files: None,
126-
private,
124+
private: torrent_info.private,
127125
path: None,
128126
root_hash: None,
129127
source: None,
@@ -296,7 +294,7 @@ pub struct DbTorrentInfo {
296294
pub pieces: String,
297295
pub piece_length: i64,
298296
#[serde(default)]
299-
pub private: Option<i64>,
297+
pub private: Option<u8>,
300298
pub root_hash: i64,
301299
}
302300

src/services/torrent_file.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub struct NewTorrentInfoRequest {
1212
pub name: String,
1313
pub pieces: String,
1414
pub piece_length: i64,
15-
pub private: Option<i64>,
15+
pub private: Option<u8>,
1616
pub root_hash: i64,
1717
pub files: Vec<TorrentFile>,
1818
pub announce_urls: Vec<Vec<String>>,
@@ -77,7 +77,7 @@ mod tests {
7777
md5sum: None,
7878
length: Some(37),
7979
files: None,
80-
private: Some(0),
80+
private: None,
8181
path: None,
8282
root_hash: None,
8383
source: None,

tests/common/contexts/torrent/asserts.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,3 @@ pub fn assert_expected_torrent_details(torrent: &TorrentDetails, expected_torren
3535
"torrent `magnet_link` mismatch"
3636
);
3737
}
38-
39-
/// Full assert for test debugging purposes.
40-
pub fn _assert_eq_torrent_details(torrent: &TorrentDetails, expected_torrent: &TorrentDetails) {
41-
assert_eq!(
42-
torrent, expected_torrent,
43-
"\nUnexpected torrent details:\n{torrent:#?} torrent details should match the previously indexed torrent:\n{expected_torrent:#?}"
44-
);
45-
}

tests/common/contexts/torrent/fixtures.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ impl TestTorrent {
137137
}
138138
}
139139

140-
pub fn info_hash(&self) -> InfoHash {
140+
pub fn info_hash_as_hex_string(&self) -> InfoHash {
141141
self.file_info.info_hash.clone()
142142
}
143143
}

tests/common/responses.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ impl BinaryResponse {
5353
bytes: response.bytes().await.unwrap().to_vec(),
5454
}
5555
}
56-
pub fn is_bittorrent_and_ok(&self) -> bool {
57-
self.is_ok() && self.is_bittorrent()
56+
pub fn is_a_bit_torrent_file(&self) -> bool {
57+
self.is_ok() && self.is_bittorrent_content_type()
5858
}
5959

60-
pub fn is_bittorrent(&self) -> bool {
60+
pub fn is_bittorrent_content_type(&self) -> bool {
6161
if let Some(content_type) = &self.content_type {
6262
return content_type == "application/x-bittorrent";
6363
}

tests/e2e/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub const ENV_VAR_E2E_CONFIG_PATH: &str = "TORRUST_IDX_BACK_E2E_CONFIG_PATH";
1919

2020
// Default values
2121

22-
pub const ENV_VAR_E2E_DEFAULT_CONFIG_PATH: &str = "./config-idx-back.local.toml";
22+
pub const ENV_VAR_E2E_DEFAULT_CONFIG_PATH: &str = "./config-idx-back.sqlite.local.toml";
2323

2424
/// Initialize configuration from file or env var.
2525
///

tests/e2e/web/api/v1/contexts/torrent/asserts.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,29 @@ use crate::common::contexts::user::responses::LoggedInUserData;
88
use crate::e2e::environment::TestEnv;
99

1010
/// The backend does not generate exactly the same torrent that was uploaded.
11-
/// So we need to update the expected torrent to match the one generated by
12-
/// the backend.
13-
pub async fn expected_torrent(mut uploaded_torrent: Torrent, env: &TestEnv, downloader: &Option<LoggedInUserData>) -> Torrent {
14-
// code-review: The backend does not generate exactly the same torrent
15-
// that was uploaded and created by the `imdl` command-line tool.
16-
// So we need to update the expected torrent to match the one generated
17-
// by the backend. For some of them it makes sense (`announce` and `announce_list`),
18-
// for others it does not.
19-
11+
///
12+
/// The backend stores the canonical version of the uploaded torrent. So we need
13+
/// to update the expected torrent to match the one generated by the backend.
14+
pub async fn canonical_torrent_for(
15+
mut uploaded_torrent: Torrent,
16+
env: &TestEnv,
17+
downloader: &Option<LoggedInUserData>,
18+
) -> Torrent {
2019
let tracker_url = env.server_settings().unwrap().tracker.url.to_string();
2120

2221
let tracker_key = match downloader {
2322
Some(logged_in_user) => get_user_tracker_key(logged_in_user, env).await,
2423
None => None,
2524
};
2625

27-
uploaded_torrent.info.private = Some(0);
2826
uploaded_torrent.announce = Some(build_announce_url(&tracker_url, &tracker_key));
29-
uploaded_torrent.encoding = None;
3027
uploaded_torrent.announce_list = Some(build_announce_list(&tracker_url, &tracker_key));
28+
29+
// These fields are not persisted in the database yet.
30+
// See https://github.com/torrust/torrust-index-backend/issues/284
31+
// They are ignore when the user uploads the torrent. So the stored
32+
// canonical torrent does not contain them.
33+
uploaded_torrent.encoding = None;
3134
uploaded_torrent.creation_date = None;
3235
uploaded_torrent.created_by = None;
3336

tests/e2e/web/api/v1/contexts/torrent/contract.rs

Lines changed: 93 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ Get torrent info:
1616

1717
mod for_guests {
1818

19-
use torrust_index_backend::utils::parse_torrent::decode_torrent;
2019
use torrust_index_backend::web::api;
2120

2221
use crate::common::client::Client;
@@ -28,7 +27,6 @@ mod for_guests {
2827
};
2928
use crate::common::http::{Query, QueryParam};
3029
use crate::e2e::environment::TestEnv;
31-
use crate::e2e::web::api::v1::contexts::torrent::asserts::expected_torrent;
3230
use crate::e2e::web::api::v1::contexts::torrent::steps::upload_random_torrent_to_index;
3331
use crate::e2e::web::api::v1::contexts::user::steps::new_logged_in_user;
3432

@@ -166,7 +164,7 @@ mod for_guests {
166164
let uploader = new_logged_in_user(&env).await;
167165
let (test_torrent, uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await;
168166

169-
let response = client.get_torrent(&test_torrent.info_hash()).await;
167+
let response = client.get_torrent(&test_torrent.info_hash_as_hex_string()).await;
170168

171169
let torrent_details_response: TorrentDetailsResponse = serde_json::from_str(&response.body).unwrap();
172170

@@ -215,29 +213,96 @@ mod for_guests {
215213
assert!(response.is_json_and_ok());
216214
}
217215

218-
#[tokio::test]
219-
async fn it_should_allow_guests_to_download_a_torrent_file_searching_by_info_hash() {
220-
let mut env = TestEnv::new();
221-
env.start(api::Version::V1).await;
216+
mod it_should_allow_guests_download_a_torrent_file_searching_by_info_hash {
222217

223-
if !env.provides_a_tracker() {
224-
println!("test skipped. It requires a tracker to be running.");
225-
return;
218+
use torrust_index_backend::utils::parse_torrent::{calculate_info_hash, decode_torrent};
219+
use torrust_index_backend::web::api;
220+
221+
use crate::common::client::Client;
222+
use crate::e2e::environment::TestEnv;
223+
use crate::e2e::web::api::v1::contexts::torrent::asserts::canonical_torrent_for;
224+
use crate::e2e::web::api::v1::contexts::torrent::steps::upload_random_torrent_to_index;
225+
use crate::e2e::web::api::v1::contexts::user::steps::new_logged_in_user;
226+
227+
#[tokio::test]
228+
async fn returning_a_bittorrent_binary_ok_response() {
229+
let mut env = TestEnv::new();
230+
env.start(api::Version::V1).await;
231+
232+
if !env.provides_a_tracker() {
233+
println!("test skipped. It requires a tracker to be running.");
234+
return;
235+
}
236+
237+
let client = Client::unauthenticated(&env.server_socket_addr().unwrap());
238+
let uploader = new_logged_in_user(&env).await;
239+
240+
// Upload
241+
let (test_torrent, _torrent_listed_in_index) = upload_random_torrent_to_index(&uploader, &env).await;
242+
243+
// Download
244+
let response = client.download_torrent(&test_torrent.info_hash_as_hex_string()).await;
245+
246+
assert!(response.is_a_bit_torrent_file());
226247
}
227248

228-
let client = Client::unauthenticated(&env.server_socket_addr().unwrap());
249+
#[tokio::test]
250+
async fn the_downloaded_torrent_should_keep_the_same_info_hash_if_the_torrent_does_not_have_non_standard_fields_in_the_info_dict(
251+
) {
252+
let mut env = TestEnv::new();
253+
env.start(api::Version::V1).await;
229254

230-
let uploader = new_logged_in_user(&env).await;
231-
let (test_torrent, _torrent_listed_in_index) = upload_random_torrent_to_index(&uploader, &env).await;
255+
if !env.provides_a_tracker() {
256+
println!("test skipped. It requires a tracker to be running.");
257+
return;
258+
}
232259

233-
let response = client.download_torrent(&test_torrent.info_hash()).await;
260+
let client = Client::unauthenticated(&env.server_socket_addr().unwrap());
261+
let uploader = new_logged_in_user(&env).await;
234262

235-
let torrent = decode_torrent(&response.bytes).expect("could not decode downloaded torrent");
236-
let uploaded_torrent =
237-
decode_torrent(&test_torrent.index_info.torrent_file.contents).expect("could not decode uploaded torrent");
238-
let expected_torrent = expected_torrent(uploaded_torrent, &env, &None).await;
239-
assert_eq!(torrent, expected_torrent);
240-
assert!(response.is_bittorrent_and_ok());
263+
// Upload
264+
let (test_torrent, _torrent_listed_in_index) = upload_random_torrent_to_index(&uploader, &env).await;
265+
266+
// Download
267+
let response = client.download_torrent(&test_torrent.info_hash_as_hex_string()).await;
268+
269+
let downloaded_torrent_info_hash = calculate_info_hash(&response.bytes);
270+
271+
assert_eq!(
272+
downloaded_torrent_info_hash.to_hex_string(),
273+
test_torrent.info_hash_as_hex_string(),
274+
"downloaded torrent info-hash does not match uploaded torrent info-hash"
275+
);
276+
}
277+
278+
#[tokio::test]
279+
async fn the_downloaded_torrent_should_be_the_canonical_version_of_the_uploaded_one() {
280+
let mut env = TestEnv::new();
281+
env.start(api::Version::V1).await;
282+
283+
if !env.provides_a_tracker() {
284+
println!("test skipped. It requires a tracker to be running.");
285+
return;
286+
}
287+
288+
let client = Client::unauthenticated(&env.server_socket_addr().unwrap());
289+
let uploader = new_logged_in_user(&env).await;
290+
291+
// Upload
292+
let (test_torrent, _torrent_listed_in_index) = upload_random_torrent_to_index(&uploader, &env).await;
293+
294+
let uploaded_torrent =
295+
decode_torrent(&test_torrent.index_info.torrent_file.contents).expect("could not decode uploaded torrent");
296+
297+
// Download
298+
let response = client.download_torrent(&test_torrent.info_hash_as_hex_string()).await;
299+
300+
let downloaded_torrent = decode_torrent(&response.bytes).expect("could not decode downloaded torrent");
301+
302+
let expected_downloaded_torrent = canonical_torrent_for(uploaded_torrent, &env, &None).await;
303+
304+
assert_eq!(downloaded_torrent, expected_downloaded_torrent);
305+
}
241306
}
242307

243308
#[tokio::test]
@@ -283,7 +348,7 @@ mod for_guests {
283348
let uploader = new_logged_in_user(&env).await;
284349
let (test_torrent, _uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await;
285350

286-
let response = client.delete_torrent(&test_torrent.info_hash()).await;
351+
let response = client.delete_torrent(&test_torrent.info_hash_as_hex_string()).await;
287352

288353
assert_eq!(response.status, 401);
289354
}
@@ -319,7 +384,7 @@ mod for_authenticated_users {
319384
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token);
320385

321386
let test_torrent = random_torrent();
322-
let info_hash = test_torrent.info_hash().clone();
387+
let info_hash = test_torrent.info_hash_as_hex_string().clone();
323388

324389
let form: UploadTorrentMultipartForm = test_torrent.index_info.into();
325390

@@ -462,7 +527,7 @@ mod for_authenticated_users {
462527
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &downloader.token);
463528

464529
// When the user downloads the torrent
465-
let response = client.download_torrent(&test_torrent.info_hash()).await;
530+
let response = client.download_torrent(&test_torrent.info_hash_as_hex_string()).await;
466531

467532
let torrent = decode_torrent(&response.bytes).expect("could not decode downloaded torrent");
468533

@@ -504,7 +569,7 @@ mod for_authenticated_users {
504569

505570
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token);
506571

507-
let response = client.delete_torrent(&test_torrent.info_hash()).await;
572+
let response = client.delete_torrent(&test_torrent.info_hash_as_hex_string()).await;
508573

509574
assert_eq!(response.status, 403);
510575
}
@@ -532,7 +597,7 @@ mod for_authenticated_users {
532597

533598
let response = client
534599
.update_torrent(
535-
&test_torrent.info_hash(),
600+
&test_torrent.info_hash_as_hex_string(),
536601
UpdateTorrentFrom {
537602
title: Some(new_title.clone()),
538603
description: Some(new_description.clone()),
@@ -577,7 +642,7 @@ mod for_authenticated_users {
577642

578643
let response = client
579644
.update_torrent(
580-
&test_torrent.info_hash(),
645+
&test_torrent.info_hash_as_hex_string(),
581646
UpdateTorrentFrom {
582647
title: Some(new_title.clone()),
583648
description: Some(new_description.clone()),
@@ -624,7 +689,7 @@ mod for_authenticated_users {
624689
let admin = new_logged_in_admin(&env).await;
625690
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &admin.token);
626691

627-
let response = client.delete_torrent(&test_torrent.info_hash()).await;
692+
let response = client.delete_torrent(&test_torrent.info_hash_as_hex_string()).await;
628693

629694
let deleted_torrent_response: DeletedTorrentResponse = serde_json::from_str(&response.body).unwrap();
630695

@@ -653,7 +718,7 @@ mod for_authenticated_users {
653718

654719
let response = client
655720
.update_torrent(
656-
&test_torrent.info_hash(),
721+
&test_torrent.info_hash_as_hex_string(),
657722
UpdateTorrentFrom {
658723
title: Some(new_title.clone()),
659724
description: Some(new_description.clone()),

0 commit comments

Comments
 (0)