diff --git a/tests/common/contexts/torrent/fixtures.rs b/tests/common/contexts/torrent/fixtures.rs index 5e89ce6e..309be5bd 100644 --- a/tests/common/contexts/torrent/fixtures.rs +++ b/tests/common/contexts/torrent/fixtures.rs @@ -137,7 +137,7 @@ impl TestTorrent { } } - pub fn info_hash(&self) -> InfoHash { + pub fn file_info_hash(&self) -> InfoHash { self.file_info.info_hash.clone() } } diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 32236100..ef246158 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -18,10 +18,12 @@ mod for_guests { use torrust_index_backend::utils::parse_torrent::decode_torrent; use torrust_index_backend::web::api; + use uuid::Uuid; use crate::common::client::Client; use crate::common::contexts::category::fixtures::software_predefined_category_id; use crate::common::contexts::torrent::asserts::assert_expected_torrent_details; + use crate::common::contexts::torrent::fixtures::TestTorrent; use crate::common::contexts::torrent::requests::InfoHash; use crate::common::contexts::torrent::responses::{ Category, File, TorrentDetails, TorrentDetailsResponse, TorrentListResponse, @@ -29,7 +31,7 @@ mod for_guests { use crate::common::http::{Query, QueryParam}; use crate::e2e::environment::TestEnv; use crate::e2e::web::api::v1::contexts::torrent::asserts::expected_torrent; - use crate::e2e::web::api::v1::contexts::torrent::steps::upload_random_torrent_to_index; + use crate::e2e::web::api::v1::contexts::torrent::steps::{upload_random_torrent_to_index, upload_test_torrent}; use crate::e2e::web::api::v1::contexts::user::steps::new_logged_in_user; #[tokio::test] @@ -166,7 +168,7 @@ mod for_guests { let uploader = new_logged_in_user(&env).await; let (test_torrent, uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - let response = client.get_torrent(&test_torrent.info_hash()).await; + let response = client.get_torrent(&test_torrent.file_info_hash()).await; let torrent_details_response: TorrentDetailsResponse = serde_json::from_str(&response.body).unwrap(); @@ -215,6 +217,49 @@ mod for_guests { assert!(response.is_json_and_ok()); } + #[tokio::test] + async fn it_should_allow_guests_to_find_torrent_details_using_a_non_canonical_info_hash() { + let mut env = TestEnv::new(); + env.start(api::Version::V1).await; + + if !env.provides_a_tracker() { + println!("test skipped. It requires a tracker to be running."); + return; + } + + let uploader = new_logged_in_user(&env).await; + let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token); + + // Sample data needed to build two torrents with the same canonical info-hash. + // Those torrents belong to the same Canonical Infohash Group. + let id = Uuid::new_v4(); + let title = format!("title-{id}"); + let file_contents = "data".to_string(); + + let mut first_torrent = TestTorrent::with_custom_info_dict_field(id, &file_contents, "custom 01"); + first_torrent.index_info.title = title.clone(); + + let first_torrent_canonical_info_hash = upload_test_torrent(&client, &first_torrent) + .await + .expect("first torrent should be uploaded"); + + let mut second_torrent = TestTorrent::with_custom_info_dict_field(id, &file_contents, "custom 02"); + second_torrent.index_info.title = format!("{title}-clone"); + + let _result = upload_test_torrent(&client, &second_torrent).await; + + // Get torrent details using the non-canonical info-hash (second torrent info-hash) + let response = client.get_torrent(&second_torrent.file_info_hash()).await; + let torrent_details_response: TorrentDetailsResponse = serde_json::from_str(&response.body).unwrap(); + + // The returned torrent info should be the same as the first torrent + assert_eq!(response.status, 200); + assert_eq!( + torrent_details_response.data.info_hash, + first_torrent_canonical_info_hash.to_hex_string() + ); + } + #[tokio::test] async fn it_should_allow_guests_to_download_a_torrent_file_searching_by_info_hash() { let mut env = TestEnv::new(); @@ -230,7 +275,7 @@ mod for_guests { let uploader = new_logged_in_user(&env).await; let (test_torrent, _torrent_listed_in_index) = upload_random_torrent_to_index(&uploader, &env).await; - let response = client.download_torrent(&test_torrent.info_hash()).await; + let response = client.download_torrent(&test_torrent.file_info_hash()).await; let torrent = decode_torrent(&response.bytes).expect("could not decode downloaded torrent"); let uploaded_torrent = @@ -283,7 +328,7 @@ mod for_guests { let uploader = new_logged_in_user(&env).await; let (test_torrent, _uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - let response = client.delete_torrent(&test_torrent.info_hash()).await; + let response = client.delete_torrent(&test_torrent.file_info_hash()).await; assert_eq!(response.status, 401); } @@ -319,7 +364,7 @@ mod for_authenticated_users { let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token); let test_torrent = random_torrent(); - let info_hash = test_torrent.info_hash().clone(); + let info_hash = test_torrent.file_info_hash().clone(); let form: UploadTorrentMultipartForm = test_torrent.index_info.into(); @@ -462,7 +507,7 @@ mod for_authenticated_users { let client = Client::authenticated(&env.server_socket_addr().unwrap(), &downloader.token); // When the user downloads the torrent - let response = client.download_torrent(&test_torrent.info_hash()).await; + let response = client.download_torrent(&test_torrent.file_info_hash()).await; let torrent = decode_torrent(&response.bytes).expect("could not decode downloaded torrent"); @@ -504,7 +549,7 @@ mod for_authenticated_users { let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token); - let response = client.delete_torrent(&test_torrent.info_hash()).await; + let response = client.delete_torrent(&test_torrent.file_info_hash()).await; assert_eq!(response.status, 403); } @@ -532,7 +577,7 @@ mod for_authenticated_users { let response = client .update_torrent( - &test_torrent.info_hash(), + &test_torrent.file_info_hash(), UpdateTorrentFrom { title: Some(new_title.clone()), description: Some(new_description.clone()), @@ -577,7 +622,7 @@ mod for_authenticated_users { let response = client .update_torrent( - &test_torrent.info_hash(), + &test_torrent.file_info_hash(), UpdateTorrentFrom { title: Some(new_title.clone()), description: Some(new_description.clone()), @@ -624,7 +669,7 @@ mod for_authenticated_users { let admin = new_logged_in_admin(&env).await; let client = Client::authenticated(&env.server_socket_addr().unwrap(), &admin.token); - let response = client.delete_torrent(&test_torrent.info_hash()).await; + let response = client.delete_torrent(&test_torrent.file_info_hash()).await; let deleted_torrent_response: DeletedTorrentResponse = serde_json::from_str(&response.body).unwrap(); @@ -653,7 +698,7 @@ mod for_authenticated_users { let response = client .update_torrent( - &test_torrent.info_hash(), + &test_torrent.file_info_hash(), UpdateTorrentFrom { title: Some(new_title.clone()), description: Some(new_description.clone()), diff --git a/tests/e2e/web/api/v1/contexts/torrent/steps.rs b/tests/e2e/web/api/v1/contexts/torrent/steps.rs index 57a9f0ba..c516b09c 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/steps.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/steps.rs @@ -1,3 +1,8 @@ +use std::str::FromStr; + +use torrust_index_backend::models::info_hash::InfoHash; +use torrust_index_backend::web::api::v1::responses::ErrorResponseData; + use crate::common::client::Client; use crate::common::contexts::torrent::fixtures::{random_torrent, TestTorrent, TorrentIndexInfo, TorrentListedInIndex}; use crate::common::contexts::torrent::forms::UploadTorrentMultipartForm; @@ -28,3 +33,27 @@ pub async fn upload_torrent(uploader: &LoggedInUserData, torrent: &TorrentIndexI TorrentListedInIndex::from(torrent.clone(), res.unwrap().data.torrent_id) } + +/// Upload a torrent to the index. +/// +/// # Errors +/// +/// Returns an `ErrorResponseData` if the response is not a 200. +pub async fn upload_test_torrent(client: &Client, test_torrent: &TestTorrent) -> Result { + let form: UploadTorrentMultipartForm = test_torrent.clone().index_info.into(); + let response = client.upload_torrent(form.into()).await; + + if response.status != 200 { + let error: ErrorResponseData = serde_json::from_str(&response.body) + .unwrap_or_else(|_| panic!("response {:#?} should be a ErrorResponseData", response.body)); + return Err(error); + } + + let uploaded_torrent_response: UploadedTorrentResponse = serde_json::from_str(&response.body).unwrap(); + let canonical_info_hash_hex = uploaded_torrent_response.data.info_hash.to_lowercase(); + + let canonical_info_hash = InfoHash::from_str(&canonical_info_hash_hex) + .unwrap_or_else(|_| panic!("Invalid info-hash in database: {canonical_info_hash_hex}")); + + Ok(canonical_info_hash) +}