From 88d3d497de51436d230d2c2dfd6ebcb82b55a049 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 26 Dec 2024 09:36:06 +0000 Subject: [PATCH 1/3] feat: add server socket address to logs in API ``` 2024-12-26T09:07:18.149759Z ERROR API: response latency_ms=0 status_code=500 Internal Server Error server_socket_addr=127.0.0.1:41579 request_id=44d8c2f6-630d-4eab-a399-65aed1dbc8ab ``` --- src/servers/apis/routes.rs | 13 +++++++------ src/servers/apis/server.rs | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/servers/apis/routes.rs b/src/servers/apis/routes.rs index c021cb21..0b0862fb 100644 --- a/src/servers/apis/routes.rs +++ b/src/servers/apis/routes.rs @@ -5,6 +5,7 @@ //! //! All the API routes have the `/api` prefix and the version number as the //! first path segment. For example: `/api/v1/torrents`. +use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; @@ -35,7 +36,7 @@ use crate::servers::logging::Latency; /// Add all API routes to the router. #[allow(clippy::needless_pass_by_value)] #[instrument(skip(tracker, access_tokens))] -pub fn router(tracker: Arc, access_tokens: Arc) -> Router { +pub fn router(tracker: Arc, access_tokens: Arc, server_socket_addr: SocketAddr) -> Router { let router = Router::new(); let api_url_prefix = "/api"; @@ -68,7 +69,7 @@ pub fn router(tracker: Arc, access_tokens: Arc) -> Router target: API_LOG_TARGET, tracing::Level::INFO, %method, %uri, %request_id, "request"); }) - .on_response(|response: &Response, latency: Duration, span: &Span| { + .on_response(move |response: &Response, latency: Duration, span: &Span| { let latency_ms = latency.as_millis(); let status_code = response.status(); let request_id = response @@ -82,20 +83,20 @@ pub fn router(tracker: Arc, access_tokens: Arc) -> Router if status_code.is_server_error() { tracing::event!( target: API_LOG_TARGET, - tracing::Level::ERROR, %latency_ms, %status_code, %request_id, "response"); + tracing::Level::ERROR, %latency_ms, %status_code, %server_socket_addr, %request_id, "response"); } else { tracing::event!( target: API_LOG_TARGET, - tracing::Level::INFO, %latency_ms, %status_code, %request_id, "response"); + tracing::Level::INFO, %latency_ms, %status_code, %server_socket_addr, %request_id, "response"); } }) .on_failure( - |failure_classification: ServerErrorsFailureClass, latency: Duration, _span: &Span| { + move |failure_classification: ServerErrorsFailureClass, latency: Duration, _span: &Span| { let latency = Latency::new(LatencyUnit::Millis, latency); tracing::event!( target: API_LOG_TARGET, - tracing::Level::ERROR, %failure_classification, %latency, "response failed"); + tracing::Level::ERROR, %failure_classification, %latency, %server_socket_addr, "response failed"); }, ), ) diff --git a/src/servers/apis/server.rs b/src/servers/apis/server.rs index 31220f49..eadadecf 100644 --- a/src/servers/apis/server.rs +++ b/src/servers/apis/server.rs @@ -243,10 +243,11 @@ impl Launcher { tx_start: Sender, rx_halt: Receiver, ) -> BoxFuture<'static, ()> { - let router = router(tracker, access_tokens); let socket = std::net::TcpListener::bind(self.bind_to).expect("Could not bind tcp_listener to address."); let address = socket.local_addr().expect("Could not get local_addr from tcp_listener."); + let router = router(tracker, access_tokens, address); + let handle = Handle::new(); tokio::task::spawn(graceful_shutdown( From cc2840f25e69e4fd7bf801c6afe5048c8afc2164 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 26 Dec 2024 10:24:35 +0000 Subject: [PATCH 2/3] feat: add an option to pass the request id in the API client Now you can send a header `x-request-id` to the API. ```rust let request_id = Uuid::new_v4(); let response = Client::new(env.get_connection_info()) .get_request_with_query( "stats", Query::params([QueryParam::new("token", "")].to_vec()), Some(headers_with_request_id(request_id)), ) .await; ``` That ID is recorded in logs. So you can use it to track the request. --- tests/servers/api/v1/client.rs | 43 ++++++++++++------- .../servers/api/v1/contract/authentication.rs | 28 +++++++++--- .../api/v1/contract/context/health_check.rs | 2 +- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/tests/servers/api/v1/client.rs b/tests/servers/api/v1/client.rs index 3d95c10c..a447805d 100644 --- a/tests/servers/api/v1/client.rs +++ b/tests/servers/api/v1/client.rs @@ -1,5 +1,7 @@ +use hyper::HeaderMap; use reqwest::Response; use serde::Serialize; +use uuid::Uuid; use crate::common::http::{Query, QueryParam, ReqwestQuery}; use crate::servers::api::connection_info::ConnectionInfo; @@ -65,7 +67,7 @@ impl Client { query.add_param(QueryParam::new("token", token)); }; - self.get_request_with_query(path, query).await + self.get_request_with_query(path, query, None).await } pub async fn post_empty(&self, path: &str) -> Response { @@ -96,12 +98,12 @@ impl Client { .unwrap() } - pub async fn get_request_with_query(&self, path: &str, params: Query) -> Response { - get(&self.base_url(path), Some(params)).await + pub async fn get_request_with_query(&self, path: &str, params: Query, headers: Option) -> Response { + get(&self.base_url(path), Some(params), headers).await } pub async fn get_request(&self, path: &str) -> Response { - get(&self.base_url(path), None).await + get(&self.base_url(path), None, None).await } fn query_with_token(&self) -> Query { @@ -116,18 +118,27 @@ impl Client { } } -pub async fn get(path: &str, query: Option) -> Response { - match query { - Some(params) => reqwest::Client::builder() - .build() - .unwrap() - .get(path) - .query(&ReqwestQuery::from(params)) - .send() - .await - .unwrap(), - None => reqwest::Client::builder().build().unwrap().get(path).send().await.unwrap(), - } +pub async fn get(path: &str, query: Option, headers: Option) -> Response { + let builder = reqwest::Client::builder().build().unwrap(); + + let builder = match query { + Some(params) => builder.get(path).query(&ReqwestQuery::from(params)), + None => builder.get(path), + }; + + let builder = match headers { + Some(headers) => builder.headers(headers), + None => builder, + }; + + builder.send().await.unwrap() +} + +/// Returns a `HeaderMap` with a request id header +pub fn headers_with_request_id(request_id: Uuid) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert("x-request-id", request_id.to_string().parse().unwrap()); + headers } #[derive(Serialize, Debug)] diff --git a/tests/servers/api/v1/contract/authentication.rs b/tests/servers/api/v1/contract/authentication.rs index 8f5ce8f5..dc50048f 100644 --- a/tests/servers/api/v1/contract/authentication.rs +++ b/tests/servers/api/v1/contract/authentication.rs @@ -1,9 +1,10 @@ use torrust_tracker_test_helpers::configuration; +use uuid::Uuid; use crate::common::http::{Query, QueryParam}; -use crate::common::logging::{self}; +use crate::common::logging::{self, logs_contains_a_line_with}; use crate::servers::api::v1::asserts::{assert_token_not_valid, assert_unauthorized}; -use crate::servers::api::v1::client::Client; +use crate::servers::api::v1::client::{headers_with_request_id, Client}; use crate::servers::api::Started; #[tokio::test] @@ -15,7 +16,7 @@ async fn should_authenticate_requests_by_using_a_token_query_param() { let token = env.get_connection_info().api_token.unwrap(); let response = Client::new(env.get_connection_info()) - .get_request_with_query("stats", Query::params([QueryParam::new("token", &token)].to_vec())) + .get_request_with_query("stats", Query::params([QueryParam::new("token", &token)].to_vec()), None) .await; assert_eq!(response.status(), 200); @@ -30,7 +31,7 @@ async fn should_not_authenticate_requests_when_the_token_is_missing() { let env = Started::new(&configuration::ephemeral().into()).await; let response = Client::new(env.get_connection_info()) - .get_request_with_query("stats", Query::default()) + .get_request_with_query("stats", Query::default(), None) .await; assert_unauthorized(response).await; @@ -44,13 +45,24 @@ async fn should_not_authenticate_requests_when_the_token_is_empty() { let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .get_request_with_query("stats", Query::params([QueryParam::new("token", "")].to_vec())) + .get_request_with_query( + "stats", + Query::params([QueryParam::new("token", "")].to_vec()), + Some(headers_with_request_id(request_id)), + ) .await; assert_token_not_valid(response).await; env.stop().await; + + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); } #[tokio::test] @@ -60,7 +72,11 @@ async fn should_not_authenticate_requests_when_the_token_is_invalid() { let env = Started::new(&configuration::ephemeral().into()).await; let response = Client::new(env.get_connection_info()) - .get_request_with_query("stats", Query::params([QueryParam::new("token", "INVALID TOKEN")].to_vec())) + .get_request_with_query( + "stats", + Query::params([QueryParam::new("token", "INVALID TOKEN")].to_vec()), + None, + ) .await; assert_token_not_valid(response).await; diff --git a/tests/servers/api/v1/contract/context/health_check.rs b/tests/servers/api/v1/contract/context/health_check.rs index 0fd3f6ea..fa6cfa09 100644 --- a/tests/servers/api/v1/contract/context/health_check.rs +++ b/tests/servers/api/v1/contract/context/health_check.rs @@ -13,7 +13,7 @@ async fn health_check_endpoint_should_return_status_ok_if_api_is_running() { let url = format!("http://{}/api/health_check", env.get_connection_info().bind_address); - let response = get(&url, None).await; + let response = get(&url, None, None).await; assert_eq!(response.status(), 200); assert_eq!(response.headers().get("content-type").unwrap(), "application/json"); From 5d49d48a74114f1eb80e60776433a71ec6ddb4a3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 26 Dec 2024 11:07:03 +0000 Subject: [PATCH 3/3] test: [#1152] write assertions when API writes errors into logs --- tests/servers/api/v1/client.rs | 92 +++++---- .../servers/api/v1/contract/authentication.rs | 22 +- .../api/v1/contract/context/auth_key.rs | 194 ++++++++++++++---- .../servers/api/v1/contract/context/stats.rs | 29 ++- .../api/v1/contract/context/torrent.rs | 120 ++++++++--- .../api/v1/contract/context/whitelist.rs | 122 +++++++++-- 6 files changed, 450 insertions(+), 129 deletions(-) diff --git a/tests/servers/api/v1/client.rs b/tests/servers/api/v1/client.rs index a447805d..63533107 100644 --- a/tests/servers/api/v1/client.rs +++ b/tests/servers/api/v1/client.rs @@ -20,82 +20,94 @@ impl Client { } } - pub async fn generate_auth_key(&self, seconds_valid: i32) -> Response { - self.post_empty(&format!("key/{}", &seconds_valid)).await + pub async fn generate_auth_key(&self, seconds_valid: i32, headers: Option) -> Response { + self.post_empty(&format!("key/{}", &seconds_valid), headers).await } - pub async fn add_auth_key(&self, add_key_form: AddKeyForm) -> Response { - self.post_form("keys", &add_key_form).await + pub async fn add_auth_key(&self, add_key_form: AddKeyForm, headers: Option) -> Response { + self.post_form("keys", &add_key_form, headers).await } - pub async fn delete_auth_key(&self, key: &str) -> Response { - self.delete(&format!("key/{}", &key)).await + pub async fn delete_auth_key(&self, key: &str, headers: Option) -> Response { + self.delete(&format!("key/{}", &key), headers).await } - pub async fn reload_keys(&self) -> Response { - self.get("keys/reload", Query::default()).await + pub async fn reload_keys(&self, headers: Option) -> Response { + self.get("keys/reload", Query::default(), headers).await } - pub async fn whitelist_a_torrent(&self, info_hash: &str) -> Response { - self.post_empty(&format!("whitelist/{}", &info_hash)).await + pub async fn whitelist_a_torrent(&self, info_hash: &str, headers: Option) -> Response { + self.post_empty(&format!("whitelist/{}", &info_hash), headers).await } - pub async fn remove_torrent_from_whitelist(&self, info_hash: &str) -> Response { - self.delete(&format!("whitelist/{}", &info_hash)).await + pub async fn remove_torrent_from_whitelist(&self, info_hash: &str, headers: Option) -> Response { + self.delete(&format!("whitelist/{}", &info_hash), headers).await } - pub async fn reload_whitelist(&self) -> Response { - self.get("whitelist/reload", Query::default()).await + pub async fn reload_whitelist(&self, headers: Option) -> Response { + self.get("whitelist/reload", Query::default(), headers).await } - pub async fn get_torrent(&self, info_hash: &str) -> Response { - self.get(&format!("torrent/{}", &info_hash), Query::default()).await + pub async fn get_torrent(&self, info_hash: &str, headers: Option) -> Response { + self.get(&format!("torrent/{}", &info_hash), Query::default(), headers).await } - pub async fn get_torrents(&self, params: Query) -> Response { - self.get("torrents", params).await + pub async fn get_torrents(&self, params: Query, headers: Option) -> Response { + self.get("torrents", params, headers).await } - pub async fn get_tracker_statistics(&self) -> Response { - self.get("stats", Query::default()).await + pub async fn get_tracker_statistics(&self, headers: Option) -> Response { + self.get("stats", Query::default(), headers).await } - pub async fn get(&self, path: &str, params: Query) -> Response { + pub async fn get(&self, path: &str, params: Query, headers: Option) -> Response { let mut query: Query = params; if let Some(token) = &self.connection_info.api_token { query.add_param(QueryParam::new("token", token)); }; - self.get_request_with_query(path, query, None).await + self.get_request_with_query(path, query, headers).await } - pub async fn post_empty(&self, path: &str) -> Response { - reqwest::Client::new() + pub async fn post_empty(&self, path: &str, headers: Option) -> Response { + let builder = reqwest::Client::new() .post(self.base_url(path).clone()) - .query(&ReqwestQuery::from(self.query_with_token())) - .send() - .await - .unwrap() + .query(&ReqwestQuery::from(self.query_with_token())); + + let builder = match headers { + Some(headers) => builder.headers(headers), + None => builder, + }; + + builder.send().await.unwrap() } - pub async fn post_form(&self, path: &str, form: &T) -> Response { - reqwest::Client::new() + pub async fn post_form(&self, path: &str, form: &T, headers: Option) -> Response { + let builder = reqwest::Client::new() .post(self.base_url(path).clone()) .query(&ReqwestQuery::from(self.query_with_token())) - .json(&form) - .send() - .await - .unwrap() + .json(&form); + + let builder = match headers { + Some(headers) => builder.headers(headers), + None => builder, + }; + + builder.send().await.unwrap() } - async fn delete(&self, path: &str) -> Response { - reqwest::Client::new() + async fn delete(&self, path: &str, headers: Option) -> Response { + let builder = reqwest::Client::new() .delete(self.base_url(path).clone()) - .query(&ReqwestQuery::from(self.query_with_token())) - .send() - .await - .unwrap() + .query(&ReqwestQuery::from(self.query_with_token())); + + let builder = match headers { + Some(headers) => builder.headers(headers), + None => builder, + }; + + builder.send().await.unwrap() } pub async fn get_request_with_query(&self, path: &str, params: Query, headers: Option) -> Response { diff --git a/tests/servers/api/v1/contract/authentication.rs b/tests/servers/api/v1/contract/authentication.rs index dc50048f..4e0cf49d 100644 --- a/tests/servers/api/v1/contract/authentication.rs +++ b/tests/servers/api/v1/contract/authentication.rs @@ -30,12 +30,19 @@ async fn should_not_authenticate_requests_when_the_token_is_missing() { let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .get_request_with_query("stats", Query::default(), None) + .get_request_with_query("stats", Query::default(), Some(headers_with_request_id(request_id))) .await; assert_unauthorized(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -57,12 +64,12 @@ async fn should_not_authenticate_requests_when_the_token_is_empty() { assert_token_not_valid(response).await; - env.stop().await; - assert!( logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), "Expected logs to contain: ERROR ... API ... request_id={request_id}" ); + + env.stop().await; } #[tokio::test] @@ -71,16 +78,23 @@ async fn should_not_authenticate_requests_when_the_token_is_invalid() { let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) .get_request_with_query( "stats", Query::params([QueryParam::new("token", "INVALID TOKEN")].to_vec()), - None, + Some(headers_with_request_id(request_id)), ) .await; assert_token_not_valid(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } diff --git a/tests/servers/api/v1/contract/context/auth_key.rs b/tests/servers/api/v1/contract/context/auth_key.rs index 9560a2f4..8ef72230 100644 --- a/tests/servers/api/v1/contract/context/auth_key.rs +++ b/tests/servers/api/v1/contract/context/auth_key.rs @@ -3,15 +3,16 @@ use std::time::Duration; use serde::Serialize; use torrust_tracker::core::auth::Key; use torrust_tracker_test_helpers::configuration; +use uuid::Uuid; -use crate::common::logging::{self}; +use crate::common::logging::{self, logs_contains_a_line_with}; use crate::servers::api::connection_info::{connection_with_invalid_token, connection_with_no_token}; use crate::servers::api::v1::asserts::{ assert_auth_key_utf8, assert_failed_to_delete_key, assert_failed_to_generate_key, assert_failed_to_reload_keys, assert_invalid_auth_key_get_param, assert_invalid_auth_key_post_param, assert_ok, assert_token_not_valid, assert_unauthorized, assert_unprocessable_auth_key_duration_param, }; -use crate::servers::api::v1::client::{AddKeyForm, Client}; +use crate::servers::api::v1::client::{headers_with_request_id, AddKeyForm, Client}; use crate::servers::api::{force_database_error, Started}; #[tokio::test] @@ -20,11 +21,16 @@ async fn should_allow_generating_a_new_random_auth_key() { let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .add_auth_key(AddKeyForm { - opt_key: None, - seconds_valid: Some(60), - }) + .add_auth_key( + AddKeyForm { + opt_key: None, + seconds_valid: Some(60), + }, + Some(headers_with_request_id(request_id)), + ) .await; let auth_key_resource = assert_auth_key_utf8(response).await; @@ -44,11 +50,16 @@ async fn should_allow_uploading_a_preexisting_auth_key() { let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .add_auth_key(AddKeyForm { - opt_key: Some("Xc1L4PbQJSFGlrgSRZl8wxSFAuMa21z5".to_string()), - seconds_valid: Some(60), - }) + .add_auth_key( + AddKeyForm { + opt_key: Some("Xc1L4PbQJSFGlrgSRZl8wxSFAuMa21z5".to_string()), + seconds_valid: Some(60), + }, + Some(headers_with_request_id(request_id)), + ) .await; let auth_key_resource = assert_auth_key_utf8(response).await; @@ -68,24 +79,44 @@ async fn should_not_allow_generating_a_new_auth_key_for_unauthenticated_users() let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) - .add_auth_key(AddKeyForm { - opt_key: None, - seconds_valid: Some(60), - }) + .add_auth_key( + AddKeyForm { + opt_key: None, + seconds_valid: Some(60), + }, + Some(headers_with_request_id(request_id)), + ) .await; assert_token_not_valid(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) - .add_auth_key(AddKeyForm { - opt_key: None, - seconds_valid: Some(60), - }) + .add_auth_key( + AddKeyForm { + opt_key: None, + seconds_valid: Some(60), + }, + Some(headers_with_request_id(request_id)), + ) .await; assert_unauthorized(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -97,15 +128,25 @@ async fn should_fail_when_the_auth_key_cannot_be_generated() { force_database_error(&env.tracker); + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .add_auth_key(AddKeyForm { - opt_key: None, - seconds_valid: Some(60), - }) + .add_auth_key( + AddKeyForm { + opt_key: None, + seconds_valid: Some(60), + }, + Some(headers_with_request_id(request_id)), + ) .await; assert_failed_to_generate_key(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -122,8 +163,10 @@ async fn should_allow_deleting_an_auth_key() { .await .unwrap(); + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .delete_auth_key(&auth_key.key.to_string()) + .delete_auth_key(&auth_key.key.to_string(), Some(headers_with_request_id(request_id))) .await; assert_ok(response).await; @@ -154,6 +197,8 @@ async fn should_fail_generating_a_new_auth_key_when_the_provided_key_is_invalid( ]; for invalid_key in invalid_keys { + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) .post_form( "keys", @@ -161,6 +206,7 @@ async fn should_fail_generating_a_new_auth_key_when_the_provided_key_is_invalid( opt_key: Some(invalid_key.to_string()), seconds_valid: 60, }, + Some(headers_with_request_id(request_id)), ) .await; @@ -190,6 +236,8 @@ async fn should_fail_generating_a_new_auth_key_when_the_key_duration_is_invalid( ]; for invalid_key_duration in invalid_key_durations { + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) .post_form( "keys", @@ -197,6 +245,7 @@ async fn should_fail_generating_a_new_auth_key_when_the_key_duration_is_invalid( opt_key: None, seconds_valid: invalid_key_duration.to_string(), }, + Some(headers_with_request_id(request_id)), ) .await; @@ -223,7 +272,11 @@ async fn should_fail_deleting_an_auth_key_when_the_key_id_is_invalid() { ]; for invalid_auth_key in &invalid_auth_keys { - let response = Client::new(env.get_connection_info()).delete_auth_key(invalid_auth_key).await; + let request_id = Uuid::new_v4(); + + let response = Client::new(env.get_connection_info()) + .delete_auth_key(invalid_auth_key, Some(headers_with_request_id(request_id))) + .await; assert_invalid_auth_key_get_param(response, invalid_auth_key).await; } @@ -246,12 +299,19 @@ async fn should_fail_when_the_auth_key_cannot_be_deleted() { force_database_error(&env.tracker); + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .delete_auth_key(&auth_key.key.to_string()) + .delete_auth_key(&auth_key.key.to_string(), Some(headers_with_request_id(request_id))) .await; assert_failed_to_delete_key(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -270,12 +330,19 @@ async fn should_not_allow_deleting_an_auth_key_for_unauthenticated_users() { .await .unwrap(); + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) - .delete_auth_key(&auth_key.key.to_string()) + .delete_auth_key(&auth_key.key.to_string(), Some(headers_with_request_id(request_id))) .await; assert_token_not_valid(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + // Generate new auth key let auth_key = env .tracker @@ -283,12 +350,19 @@ async fn should_not_allow_deleting_an_auth_key_for_unauthenticated_users() { .await .unwrap(); + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) - .delete_auth_key(&auth_key.key.to_string()) + .delete_auth_key(&auth_key.key.to_string(), Some(headers_with_request_id(request_id))) .await; assert_unauthorized(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -304,7 +378,11 @@ async fn should_allow_reloading_keys() { .await .unwrap(); - let response = Client::new(env.get_connection_info()).reload_keys().await; + let request_id = Uuid::new_v4(); + + let response = Client::new(env.get_connection_info()) + .reload_keys(Some(headers_with_request_id(request_id))) + .await; assert_ok(response).await; @@ -317,7 +395,9 @@ async fn should_fail_when_keys_cannot_be_reloaded() { let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); let seconds_valid = 60; + env.tracker .generate_auth_key(Some(Duration::from_secs(seconds_valid))) .await @@ -325,10 +405,17 @@ async fn should_fail_when_keys_cannot_be_reloaded() { force_database_error(&env.tracker); - let response = Client::new(env.get_connection_info()).reload_keys().await; + let response = Client::new(env.get_connection_info()) + .reload_keys(Some(headers_with_request_id(request_id))) + .await; assert_failed_to_reload_keys(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -344,18 +431,32 @@ async fn should_not_allow_reloading_keys_for_unauthenticated_users() { .await .unwrap(); + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) - .reload_keys() + .reload_keys(Some(headers_with_request_id(request_id))) .await; assert_token_not_valid(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) - .reload_keys() + .reload_keys(Some(headers_with_request_id(request_id))) .await; assert_unauthorized(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -363,14 +464,15 @@ mod deprecated_generate_key_endpoint { use torrust_tracker::core::auth::Key; use torrust_tracker_test_helpers::configuration; + use uuid::Uuid; - use crate::common::logging::{self}; + use crate::common::logging::{self, logs_contains_a_line_with}; use crate::servers::api::connection_info::{connection_with_invalid_token, connection_with_no_token}; use crate::servers::api::v1::asserts::{ assert_auth_key_utf8, assert_failed_to_generate_key, assert_invalid_key_duration_param, assert_token_not_valid, assert_unauthorized, }; - use crate::servers::api::v1::client::Client; + use crate::servers::api::v1::client::{headers_with_request_id, Client}; use crate::servers::api::{force_database_error, Started}; #[tokio::test] @@ -381,7 +483,9 @@ mod deprecated_generate_key_endpoint { let seconds_valid = 60; - let response = Client::new(env.get_connection_info()).generate_auth_key(seconds_valid).await; + let response = Client::new(env.get_connection_info()) + .generate_auth_key(seconds_valid, None) + .await; let auth_key_resource = assert_auth_key_utf8(response).await; @@ -400,21 +504,27 @@ mod deprecated_generate_key_endpoint { let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); let seconds_valid = 60; let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) - .generate_auth_key(seconds_valid) + .generate_auth_key(seconds_valid, Some(headers_with_request_id(request_id))) .await; assert_token_not_valid(response).await; let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) - .generate_auth_key(seconds_valid) + .generate_auth_key(seconds_valid, None) .await; assert_unauthorized(response).await; env.stop().await; + + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); } #[tokio::test] @@ -431,7 +541,7 @@ mod deprecated_generate_key_endpoint { for invalid_key_duration in invalid_key_durations { let response = Client::new(env.get_connection_info()) - .post_empty(&format!("key/{invalid_key_duration}")) + .post_empty(&format!("key/{invalid_key_duration}"), None) .await; assert_invalid_key_duration_param(response, invalid_key_duration).await; @@ -448,11 +558,19 @@ mod deprecated_generate_key_endpoint { force_database_error(&env.tracker); + let request_id = Uuid::new_v4(); let seconds_valid = 60; - let response = Client::new(env.get_connection_info()).generate_auth_key(seconds_valid).await; + let response = Client::new(env.get_connection_info()) + .generate_auth_key(seconds_valid, Some(headers_with_request_id(request_id))) + .await; assert_failed_to_generate_key(response).await; env.stop().await; + + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); } } diff --git a/tests/servers/api/v1/contract/context/stats.rs b/tests/servers/api/v1/contract/context/stats.rs index e05107d2..d49d0353 100644 --- a/tests/servers/api/v1/contract/context/stats.rs +++ b/tests/servers/api/v1/contract/context/stats.rs @@ -4,11 +4,12 @@ use bittorrent_primitives::info_hash::InfoHash; use torrust_tracker::servers::apis::v1::context::stats::resources::Stats; use torrust_tracker_primitives::peer::fixture::PeerBuilder; use torrust_tracker_test_helpers::configuration; +use uuid::Uuid; -use crate::common::logging::{self}; +use crate::common::logging::{self, logs_contains_a_line_with}; use crate::servers::api::connection_info::{connection_with_invalid_token, connection_with_no_token}; use crate::servers::api::v1::asserts::{assert_stats, assert_token_not_valid, assert_unauthorized}; -use crate::servers::api::v1::client::Client; +use crate::servers::api::v1::client::{headers_with_request_id, Client}; use crate::servers::api::Started; #[tokio::test] @@ -22,7 +23,11 @@ async fn should_allow_getting_tracker_statistics() { &PeerBuilder::default().into(), ); - let response = Client::new(env.get_connection_info()).get_tracker_statistics().await; + let request_id = Uuid::new_v4(); + + let response = Client::new(env.get_connection_info()) + .get_tracker_statistics(Some(headers_with_request_id(request_id))) + .await; assert_stats( response, @@ -65,17 +70,31 @@ async fn should_not_allow_getting_tracker_statistics_for_unauthenticated_users() let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) - .get_tracker_statistics() + .get_tracker_statistics(Some(headers_with_request_id(request_id))) .await; assert_token_not_valid(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) - .get_tracker_statistics() + .get_tracker_statistics(Some(headers_with_request_id(request_id))) .await; assert_unauthorized(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } diff --git a/tests/servers/api/v1/contract/context/torrent.rs b/tests/servers/api/v1/contract/context/torrent.rs index 55c25d22..b741a1a6 100644 --- a/tests/servers/api/v1/contract/context/torrent.rs +++ b/tests/servers/api/v1/contract/context/torrent.rs @@ -5,15 +5,16 @@ use torrust_tracker::servers::apis::v1::context::torrent::resources::peer::Peer; use torrust_tracker::servers::apis::v1::context::torrent::resources::torrent::{self, Torrent}; use torrust_tracker_primitives::peer::fixture::PeerBuilder; use torrust_tracker_test_helpers::configuration; +use uuid::Uuid; use crate::common::http::{Query, QueryParam}; -use crate::common::logging::{self}; +use crate::common::logging::{self, logs_contains_a_line_with}; use crate::servers::api::connection_info::{connection_with_invalid_token, connection_with_no_token}; use crate::servers::api::v1::asserts::{ assert_bad_request, assert_invalid_infohash_param, assert_not_found, assert_token_not_valid, assert_torrent_info, assert_torrent_list, assert_torrent_not_known, assert_unauthorized, }; -use crate::servers::api::v1::client::Client; +use crate::servers::api::v1::client::{headers_with_request_id, Client}; use crate::servers::api::v1::contract::fixtures::{ invalid_infohashes_returning_bad_request, invalid_infohashes_returning_not_found, }; @@ -29,7 +30,11 @@ async fn should_allow_getting_all_torrents() { env.add_torrent_peer(&info_hash, &PeerBuilder::default().into()); - let response = Client::new(env.get_connection_info()).get_torrents(Query::empty()).await; + let request_id = Uuid::new_v4(); + + let response = Client::new(env.get_connection_info()) + .get_torrents(Query::empty(), Some(headers_with_request_id(request_id))) + .await; assert_torrent_list( response, @@ -58,8 +63,13 @@ async fn should_allow_limiting_the_torrents_in_the_result() { env.add_torrent_peer(&info_hash_1, &PeerBuilder::default().into()); env.add_torrent_peer(&info_hash_2, &PeerBuilder::default().into()); + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .get_torrents(Query::params([QueryParam::new("limit", "1")].to_vec())) + .get_torrents( + Query::params([QueryParam::new("limit", "1")].to_vec()), + Some(headers_with_request_id(request_id)), + ) .await; assert_torrent_list( @@ -89,8 +99,13 @@ async fn should_allow_the_torrents_result_pagination() { env.add_torrent_peer(&info_hash_1, &PeerBuilder::default().into()); env.add_torrent_peer(&info_hash_2, &PeerBuilder::default().into()); + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .get_torrents(Query::params([QueryParam::new("offset", "1")].to_vec())) + .get_torrents( + Query::params([QueryParam::new("offset", "1")].to_vec()), + Some(headers_with_request_id(request_id)), + ) .await; assert_torrent_list( @@ -119,14 +134,19 @@ async fn should_allow_getting_a_list_of_torrents_providing_infohashes() { env.add_torrent_peer(&info_hash_1, &PeerBuilder::default().into()); env.add_torrent_peer(&info_hash_2, &PeerBuilder::default().into()); + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .get_torrents(Query::params( - [ - QueryParam::new("info_hash", "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d"), // DevSkim: ignore DS173237 - QueryParam::new("info_hash", "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d"), // DevSkim: ignore DS173237 - ] - .to_vec(), - )) + .get_torrents( + Query::params( + [ + QueryParam::new("info_hash", "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d"), // DevSkim: ignore DS173237 + QueryParam::new("info_hash", "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d"), // DevSkim: ignore DS173237 + ] + .to_vec(), + ), + Some(headers_with_request_id(request_id)), + ) .await; assert_torrent_list( @@ -160,8 +180,13 @@ async fn should_fail_getting_torrents_when_the_offset_query_parameter_cannot_be_ let invalid_offsets = [" ", "-1", "1.1", "INVALID OFFSET"]; for invalid_offset in &invalid_offsets { + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .get_torrents(Query::params([QueryParam::new("offset", invalid_offset)].to_vec())) + .get_torrents( + Query::params([QueryParam::new("offset", invalid_offset)].to_vec()), + Some(headers_with_request_id(request_id)), + ) .await; assert_bad_request(response, "Failed to deserialize query string: invalid digit found in string").await; @@ -179,8 +204,13 @@ async fn should_fail_getting_torrents_when_the_limit_query_parameter_cannot_be_p let invalid_limits = [" ", "-1", "1.1", "INVALID LIMIT"]; for invalid_limit in &invalid_limits { + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .get_torrents(Query::params([QueryParam::new("limit", invalid_limit)].to_vec())) + .get_torrents( + Query::params([QueryParam::new("limit", invalid_limit)].to_vec()), + Some(headers_with_request_id(request_id)), + ) .await; assert_bad_request(response, "Failed to deserialize query string: invalid digit found in string").await; @@ -198,8 +228,13 @@ async fn should_fail_getting_torrents_when_the_info_hash_parameter_is_invalid() let invalid_info_hashes = [" ", "-1", "1.1", "INVALID INFO_HASH"]; for invalid_info_hash in &invalid_info_hashes { + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .get_torrents(Query::params([QueryParam::new("info_hash", invalid_info_hash)].to_vec())) + .get_torrents( + Query::params([QueryParam::new("info_hash", invalid_info_hash)].to_vec()), + Some(headers_with_request_id(request_id)), + ) .await; assert_bad_request( @@ -218,18 +253,32 @@ async fn should_not_allow_getting_torrents_for_unauthenticated_users() { let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) - .get_torrents(Query::empty()) + .get_torrents(Query::empty(), Some(headers_with_request_id(request_id))) .await; assert_token_not_valid(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) - .get_torrents(Query::default()) + .get_torrents(Query::default(), Some(headers_with_request_id(request_id))) .await; assert_unauthorized(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -245,8 +294,10 @@ async fn should_allow_getting_a_torrent_info() { env.add_torrent_peer(&info_hash, &peer); + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .get_torrent(&info_hash.to_string()) + .get_torrent(&info_hash.to_string(), Some(headers_with_request_id(request_id))) .await; assert_torrent_info( @@ -270,10 +321,11 @@ async fn should_fail_while_getting_a_torrent_info_when_the_torrent_does_not_exis let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); let info_hash = InfoHash::from_str("9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d").unwrap(); let response = Client::new(env.get_connection_info()) - .get_torrent(&info_hash.to_string()) + .get_torrent(&info_hash.to_string(), Some(headers_with_request_id(request_id))) .await; assert_torrent_not_known(response).await; @@ -288,13 +340,21 @@ async fn should_fail_getting_a_torrent_info_when_the_provided_infohash_is_invali let env = Started::new(&configuration::ephemeral().into()).await; for invalid_infohash in &invalid_infohashes_returning_bad_request() { - let response = Client::new(env.get_connection_info()).get_torrent(invalid_infohash).await; + let request_id = Uuid::new_v4(); + + let response = Client::new(env.get_connection_info()) + .get_torrent(invalid_infohash, Some(headers_with_request_id(request_id))) + .await; assert_invalid_infohash_param(response, invalid_infohash).await; } for invalid_infohash in &invalid_infohashes_returning_not_found() { - let response = Client::new(env.get_connection_info()).get_torrent(invalid_infohash).await; + let request_id = Uuid::new_v4(); + + let response = Client::new(env.get_connection_info()) + .get_torrent(invalid_infohash, Some(headers_with_request_id(request_id))) + .await; assert_not_found(response).await; } @@ -312,17 +372,31 @@ async fn should_not_allow_getting_a_torrent_info_for_unauthenticated_users() { env.add_torrent_peer(&info_hash, &PeerBuilder::default().into()); + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) - .get_torrent(&info_hash.to_string()) + .get_torrent(&info_hash.to_string(), Some(headers_with_request_id(request_id))) .await; assert_token_not_valid(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) - .get_torrent(&info_hash.to_string()) + .get_torrent(&info_hash.to_string(), Some(headers_with_request_id(request_id))) .await; assert_unauthorized(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } diff --git a/tests/servers/api/v1/contract/context/whitelist.rs b/tests/servers/api/v1/contract/context/whitelist.rs index 2be1706f..d0a80e96 100644 --- a/tests/servers/api/v1/contract/context/whitelist.rs +++ b/tests/servers/api/v1/contract/context/whitelist.rs @@ -2,14 +2,15 @@ use std::str::FromStr; use bittorrent_primitives::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; +use uuid::Uuid; -use crate::common::logging::{self}; +use crate::common::logging::{self, logs_contains_a_line_with}; use crate::servers::api::connection_info::{connection_with_invalid_token, connection_with_no_token}; use crate::servers::api::v1::asserts::{ assert_failed_to_reload_whitelist, assert_failed_to_remove_torrent_from_whitelist, assert_failed_to_whitelist_torrent, assert_invalid_infohash_param, assert_not_found, assert_ok, assert_token_not_valid, assert_unauthorized, }; -use crate::servers::api::v1::client::Client; +use crate::servers::api::v1::client::{headers_with_request_id, Client}; use crate::servers::api::v1::contract::fixtures::{ invalid_infohashes_returning_bad_request, invalid_infohashes_returning_not_found, }; @@ -21,9 +22,12 @@ async fn should_allow_whitelisting_a_torrent() { let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); let info_hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); - let response = Client::new(env.get_connection_info()).whitelist_a_torrent(&info_hash).await; + let response = Client::new(env.get_connection_info()) + .whitelist_a_torrent(&info_hash, Some(headers_with_request_id(request_id))) + .await; assert_ok(response).await; assert!( @@ -45,10 +49,18 @@ async fn should_allow_whitelisting_a_torrent_that_has_been_already_whitelisted() let api_client = Client::new(env.get_connection_info()); - let response = api_client.whitelist_a_torrent(&info_hash).await; + let request_id = Uuid::new_v4(); + + let response = api_client + .whitelist_a_torrent(&info_hash, Some(headers_with_request_id(request_id))) + .await; assert_ok(response).await; - let response = api_client.whitelist_a_torrent(&info_hash).await; + let request_id = Uuid::new_v4(); + + let response = api_client + .whitelist_a_torrent(&info_hash, Some(headers_with_request_id(request_id))) + .await; assert_ok(response).await; env.stop().await; @@ -62,18 +74,32 @@ async fn should_not_allow_whitelisting_a_torrent_for_unauthenticated_users() { let info_hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) - .whitelist_a_torrent(&info_hash) + .whitelist_a_torrent(&info_hash, Some(headers_with_request_id(request_id))) .await; assert_token_not_valid(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) - .whitelist_a_torrent(&info_hash) + .whitelist_a_torrent(&info_hash, Some(headers_with_request_id(request_id))) .await; assert_unauthorized(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -87,10 +113,19 @@ async fn should_fail_when_the_torrent_cannot_be_whitelisted() { force_database_error(&env.tracker); - let response = Client::new(env.get_connection_info()).whitelist_a_torrent(&info_hash).await; + let request_id = Uuid::new_v4(); + + let response = Client::new(env.get_connection_info()) + .whitelist_a_torrent(&info_hash, Some(headers_with_request_id(request_id))) + .await; assert_failed_to_whitelist_torrent(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -100,17 +135,21 @@ async fn should_fail_whitelisting_a_torrent_when_the_provided_infohash_is_invali let env = Started::new(&configuration::ephemeral().into()).await; + let request_id = Uuid::new_v4(); + for invalid_infohash in &invalid_infohashes_returning_bad_request() { let response = Client::new(env.get_connection_info()) - .whitelist_a_torrent(invalid_infohash) + .whitelist_a_torrent(invalid_infohash, Some(headers_with_request_id(request_id))) .await; assert_invalid_infohash_param(response, invalid_infohash).await; } + let request_id = Uuid::new_v4(); + for invalid_infohash in &invalid_infohashes_returning_not_found() { let response = Client::new(env.get_connection_info()) - .whitelist_a_torrent(invalid_infohash) + .whitelist_a_torrent(invalid_infohash, Some(headers_with_request_id(request_id))) .await; assert_not_found(response).await; @@ -127,10 +166,13 @@ async fn should_allow_removing_a_torrent_from_the_whitelist() { let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash = InfoHash::from_str(&hash).unwrap(); + env.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .remove_torrent_from_whitelist(&hash) + .remove_torrent_from_whitelist(&hash, Some(headers_with_request_id(request_id))) .await; assert_ok(response).await; @@ -147,8 +189,10 @@ async fn should_not_fail_trying_to_remove_a_non_whitelisted_torrent_from_the_whi let non_whitelisted_torrent_hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .remove_torrent_from_whitelist(&non_whitelisted_torrent_hash) + .remove_torrent_from_whitelist(&non_whitelisted_torrent_hash, Some(headers_with_request_id(request_id))) .await; assert_ok(response).await; @@ -163,16 +207,20 @@ async fn should_fail_removing_a_torrent_from_the_whitelist_when_the_provided_inf let env = Started::new(&configuration::ephemeral().into()).await; for invalid_infohash in &invalid_infohashes_returning_bad_request() { + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .remove_torrent_from_whitelist(invalid_infohash) + .remove_torrent_from_whitelist(invalid_infohash, Some(headers_with_request_id(request_id))) .await; assert_invalid_infohash_param(response, invalid_infohash).await; } for invalid_infohash in &invalid_infohashes_returning_not_found() { + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .remove_torrent_from_whitelist(invalid_infohash) + .remove_torrent_from_whitelist(invalid_infohash, Some(headers_with_request_id(request_id))) .await; assert_not_found(response).await; @@ -193,12 +241,19 @@ async fn should_fail_when_the_torrent_cannot_be_removed_from_the_whitelist() { force_database_error(&env.tracker); + let request_id = Uuid::new_v4(); + let response = Client::new(env.get_connection_info()) - .remove_torrent_from_whitelist(&hash) + .remove_torrent_from_whitelist(&hash, Some(headers_with_request_id(request_id))) .await; assert_failed_to_remove_torrent_from_whitelist(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -212,19 +267,35 @@ async fn should_not_allow_removing_a_torrent_from_the_whitelist_for_unauthentica let info_hash = InfoHash::from_str(&hash).unwrap(); env.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) - .remove_torrent_from_whitelist(&hash) + .remove_torrent_from_whitelist(&hash, Some(headers_with_request_id(request_id))) .await; assert_token_not_valid(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); + + let request_id = Uuid::new_v4(); + let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) - .remove_torrent_from_whitelist(&hash) + .remove_torrent_from_whitelist(&hash, Some(headers_with_request_id(request_id))) .await; assert_unauthorized(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; } @@ -238,7 +309,11 @@ async fn should_allow_reload_the_whitelist_from_the_database() { let info_hash = InfoHash::from_str(&hash).unwrap(); env.tracker.add_torrent_to_whitelist(&info_hash).await.unwrap(); - let response = Client::new(env.get_connection_info()).reload_whitelist().await; + let request_id = Uuid::new_v4(); + + let response = Client::new(env.get_connection_info()) + .reload_whitelist(Some(headers_with_request_id(request_id))) + .await; assert_ok(response).await; /* todo: this assert fails because the whitelist has not been reloaded yet. @@ -267,9 +342,18 @@ async fn should_fail_when_the_whitelist_cannot_be_reloaded_from_the_database() { force_database_error(&env.tracker); - let response = Client::new(env.get_connection_info()).reload_whitelist().await; + let request_id = Uuid::new_v4(); + + let response = Client::new(env.get_connection_info()) + .reload_whitelist(Some(headers_with_request_id(request_id))) + .await; assert_failed_to_reload_whitelist(response).await; + assert!( + logs_contains_a_line_with(&["ERROR", "API", &format!("{request_id}")]), + "Expected logs to contain: ERROR ... API ... request_id={request_id}" + ); + env.stop().await; }