Skip to content

Commit

Permalink
test(http): [#159] add tests for announce request in private mode
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Jan 27, 2023
1 parent 11492a3 commit badb791
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 11 deletions.
10 changes: 10 additions & 0 deletions src/tracker/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ impl Key {
None
}
}

/// # Panics
///
/// Will fail if the key id is not a valid key id.
#[must_use]
pub fn id(&self) -> KeyId {
// todo: replace the type of field `key` with type `KeyId`.
// The constructor should fail if an invalid KeyId is provided.
KeyId::from_str(&self.key).unwrap()
}
}

#[derive(Debug, Display, PartialEq, Clone)]
Expand Down
30 changes: 30 additions & 0 deletions tests/http/asserts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,33 @@ pub async fn assert_torrent_not_in_whitelist_error_response(response: Response)
};
assert_eq!(error_response, expected_error_response);
}

pub async fn assert_peer_not_authenticated_error_response(response: Response) {
assert_eq!(response.status(), 200);
let body = response.text().await.unwrap();
let error_response: Error = serde_bencode::from_str(&body).unwrap_or_else(|_| {
panic!(
"response body should be a valid bencoded string for the 'peer not authenticated' error, got \"{}\"",
&body
)
});
let expected_error_response = Error {
failure_reason: "peer not authenticated".to_string(),
};
assert_eq!(error_response, expected_error_response);
}

pub async fn assert_invalid_authentication_key_error_response(response: Response) {
assert_eq!(response.status(), 200);
let body = response.text().await.unwrap();
let error_response: Error = serde_bencode::from_str(&body).unwrap_or_else(|_| {
panic!(
"response body should be a valid bencoded string for the 'invalid authentication key' error, got \"{}\"",
&body
)
});
let expected_error_response = Error {
failure_reason: "invalid authentication key".to_string(),
};
assert_eq!(error_response, expected_error_response);
}
51 changes: 44 additions & 7 deletions tests/http/client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::net::IpAddr;

use reqwest::{Client as ReqwestClient, Response};
use torrust_tracker::tracker::auth::KeyId;

use super::connection_info::ConnectionInfo;
use super::requests::AnnounceQuery;
Expand All @@ -9,13 +10,23 @@ use super::requests::AnnounceQuery;
pub struct Client {
connection_info: ConnectionInfo,
reqwest_client: ReqwestClient,
key_id: Option<KeyId>,
}

/// URL components in this context:
///
/// ```text
/// http://127.0.0.1:62304/announce/YZ....rJ?info_hash=%9C8B%22%13%E3%0B%FF%21%2B0%C3%60%D2o%9A%02%13d%22
/// \_____________________/\_______________/ \__________________________________________________________/
/// | | |
/// base url path query
/// ```
impl Client {
pub fn new(connection_info: ConnectionInfo) -> Self {
Self {
connection_info,
reqwest_client: reqwest::Client::builder().build().unwrap(),
key_id: None,
}
}

Expand All @@ -24,31 +35,57 @@ impl Client {
Self {
connection_info,
reqwest_client: reqwest::Client::builder().local_address(local_address).build().unwrap(),
key_id: None,
}
}

pub fn authenticated(connection_info: ConnectionInfo, key_id: KeyId) -> Self {
Self {
connection_info,
reqwest_client: reqwest::Client::builder().build().unwrap(),
key_id: Some(key_id),
}
}

pub async fn announce(&self, query: &AnnounceQuery) -> Response {
self.get(&format!("announce?{query}")).await
self.get(&self.build_announce_path_and_query(query)).await
}

pub async fn announce_with_header(&self, query: &AnnounceQuery, key: &str, value: &str) -> Response {
self.get_with_header(&format!("announce?{query}"), key, value).await
pub async fn announce_with_header(&self, query: &AnnounceQuery, key_id: &str, value: &str) -> Response {
self.get_with_header(&self.build_announce_path_and_query(query), key_id, value)
.await
}

pub async fn get(&self, path: &str) -> Response {
self.reqwest_client.get(self.base_url(path)).send().await.unwrap()
self.reqwest_client.get(self.build_url(path)).send().await.unwrap()
}

pub async fn get_with_header(&self, path: &str, key: &str, value: &str) -> Response {
self.reqwest_client
.get(self.base_url(path))
.get(self.build_url(path))
.header(key, value)
.send()
.await
.unwrap()
}

fn base_url(&self, path: &str) -> String {
format!("http://{}/{path}", &self.connection_info.bind_address)
fn build_announce_path_and_query(&self, query: &AnnounceQuery) -> String {
format!("{}?{query}", self.build_path("announce"))
}

fn build_path(&self, path: &str) -> String {
match &self.key_id {
Some(key_id) => format!("{path}/{key_id}"),
None => path.to_string(),
}
}

fn build_url(&self, path: &str) -> String {
let base_url = self.base_url();
format!("{base_url}{path}")
}

fn base_url(&self) -> String {
format!("http://{}/", &self.connection_info.bind_address)
}
}
6 changes: 3 additions & 3 deletions tests/http/connection_info.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use torrust_tracker::tracker::auth::Key;
use torrust_tracker::tracker::auth::KeyId;

#[derive(Clone, Debug)]
pub struct ConnectionInfo {
pub bind_address: String,
pub aut_key: Option<Key>,
pub key_id: Option<KeyId>,
}

impl ConnectionInfo {
pub fn anonymous(bind_address: &str) -> Self {
Self {
bind_address: bind_address.to_string(),
aut_key: None,
key_id: None,
}
}
}
7 changes: 7 additions & 0 deletions tests/http/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ pub async fn start_whitelisted_http_tracker() -> Server {
start_custom_http_tracker(Arc::new(configuration)).await
}

/// Starts a HTTP tracker with mode "listed"
pub async fn start_private_http_tracker() -> Server {
let mut configuration = ephemeral_configuration();
configuration.mode = Mode::Private;
start_custom_http_tracker(Arc::new(configuration)).await
}

/// Starts a HTTP tracker with a wildcard IPV6 address.
/// The configuration in the `config.toml` file would be like this:
///
Expand Down
60 changes: 59 additions & 1 deletion tests/http_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,65 @@ mod http_tracker_server {

mod configured_as_private {

mod and_receiving_an_announce_request {}
mod and_receiving_an_announce_request {
use std::str::FromStr;
use std::time::Duration;

use torrust_tracker::protocol::info_hash::InfoHash;
use torrust_tracker::tracker::auth::KeyId;

use crate::http::asserts::{
assert_invalid_authentication_key_error_response, assert_is_announce_response,
assert_peer_not_authenticated_error_response,
};
use crate::http::client::Client;
use crate::http::requests::AnnounceQueryBuilder;
use crate::http::server::start_private_http_tracker;

#[tokio::test]
async fn should_respond_to_peers_providing_a_valid_authentication_key() {
let http_tracker_server = start_private_http_tracker().await;

let key = http_tracker_server
.tracker
.generate_auth_key(Duration::from_secs(60))
.await
.unwrap();

let response = Client::authenticated(http_tracker_server.get_connection_info(), key.id())
.announce(&AnnounceQueryBuilder::default().query())
.await;

assert_is_announce_response(response).await;
}

#[tokio::test]
async fn should_fail_if_the_peer_has_not_provided_the_authentication_key() {
let http_tracker_server = start_private_http_tracker().await;

let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap();

let response = Client::new(http_tracker_server.get_connection_info())
.announce(&AnnounceQueryBuilder::default().with_info_hash(&info_hash).query())
.await;

assert_peer_not_authenticated_error_response(response).await;
}

#[tokio::test]
async fn should_fail_if_the_peer_authentication_key_is_not_valid() {
let http_tracker_server = start_private_http_tracker().await;

// The tracker does not have this key
let unregistered_key_id = KeyId::from_str("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap();

let response = Client::authenticated(http_tracker_server.get_connection_info(), unregistered_key_id)
.announce(&AnnounceQueryBuilder::default().query())
.await;

assert_invalid_authentication_key_error_response(response).await;
}
}

mod receiving_an_scrape_request {}
}
Expand Down

0 comments on commit badb791

Please sign in to comment.