diff --git a/.gitignore b/.gitignore index 8bfa717b7..fd83ee918 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ /tracker.toml callgrind.out codecov.json +integration_tests_sqlite3.db lcov.info perf.data* rustc-ice-*.txt diff --git a/Cargo.lock b/Cargo.lock index 076449944..055c02a9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4521,6 +4521,8 @@ dependencies = [ "anyhow", "axum-server", "bittorrent-http-tracker-core", + "bittorrent-primitives", + "bittorrent-tracker-client", "bittorrent-tracker-core", "bittorrent-udp-tracker-core", "chrono", diff --git a/Cargo.toml b/Cargo.toml index bcac4bf66..91393ad72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,8 @@ tracing = "0" tracing-subscriber = { version = "0", features = ["json"] } [dev-dependencies] +bittorrent-primitives = "0.1.0" +bittorrent-tracker-client = { version = "3.0.0-develop", path = "packages/tracker-client" } local-ip-address = "0" mockall = "0" torrust-rest-tracker-api-client = { version = "3.0.0-develop", path = "packages/rest-tracker-api-client" } diff --git a/src/app.rs b/src/app.rs index fb8a459ea..007eb16d0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -28,9 +28,20 @@ use torrust_server_lib::registar::Registar; use torrust_tracker_configuration::Configuration; use tracing::instrument; +use crate::bootstrap; use crate::bootstrap::jobs::{health_check_api, http_tracker, torrent_cleanup, tracker_apis, udp_tracker}; use crate::container::AppContainer; +pub async fn run() -> (Arc, Vec>, Registar) { + let (config, app_container) = bootstrap::app::setup(); + + let app_container = Arc::new(app_container); + + let (jobs, registar) = start(&config, &app_container).await; + + (app_container, jobs, registar) +} + /// # Panics /// /// Will panic if: @@ -38,7 +49,7 @@ use crate::container::AppContainer; /// - Can't retrieve tracker keys from database. /// - Can't load whitelist from database. #[instrument(skip(config, app_container))] -pub async fn start(config: &Configuration, app_container: &Arc) -> Vec> { +pub async fn start(config: &Configuration, app_container: &Arc) -> (Vec>, Registar) { if config.http_api.is_none() && (config.udp_trackers.is_none() || config.udp_trackers.as_ref().map_or(true, std::vec::Vec::is_empty)) && (config.http_trackers.is_none() || config.http_trackers.as_ref().map_or(true, std::vec::Vec::is_empty)) @@ -138,8 +149,10 @@ pub async fn start(config: &Configuration, app_container: &Arc) -> )); } + println!("Registar entries: {:?}", registar.entries()); + // Start Health Check API jobs.push(health_check_api::start_job(&config.health_check_api, registar.entries()).await); - jobs + (jobs, registar) } diff --git a/src/console/profiling.rs b/src/console/profiling.rs index f3829c073..426712c34 100644 --- a/src/console/profiling.rs +++ b/src/console/profiling.rs @@ -157,12 +157,11 @@ //! kcachegrind callgrind.out //! ``` use std::env; -use std::sync::Arc; use std::time::Duration; use tokio::time::sleep; -use crate::{app, bootstrap}; +use crate::app; pub async fn run() { // Parse command line arguments @@ -180,11 +179,7 @@ pub async fn run() { return; }; - let (config, app_container) = bootstrap::app::setup(); - - let app_container = Arc::new(app_container); - - let jobs = app::start(&config, &app_container).await; + let (_app_container, jobs, _registar) = app::run().await; // Run the tracker for a fixed duration let run_duration = sleep(Duration::from_secs(duration_secs)); diff --git a/src/main.rs b/src/main.rs index 77f6e32a3..cc7c202c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,8 @@ -use std::sync::Arc; - -use torrust_tracker_lib::{app, bootstrap}; +use torrust_tracker_lib::app; #[tokio::main] async fn main() { - let (config, app_container) = bootstrap::app::setup(); - - let app_container = Arc::new(app_container); - - let jobs = app::start(&config, &app_container).await; + let (_app_container, jobs, _registar) = app::run().await; // handle the signals tokio::select! { diff --git a/tests/integration.rs b/tests/integration.rs index 6a139e047..92289c415 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,13 +1,14 @@ //! Scaffolding for integration tests. //! +//! Integration tests are used to test the interaction between multiple modules, +//! multiple running trackers, etc. Tests for one specific module should be in +//! the corresponding package. +//! //! ```text //! cargo test --test integration //! ``` mod servers; -// todo: there is only one test example that was copied from other package. -// We have to add tests for the whole app. - use torrust_tracker_clock::clock; /// This code needs to be copied into each crate. diff --git a/tests/servers/api/contract/mod.rs b/tests/servers/api/contract/mod.rs new file mode 100644 index 000000000..9d34677fc --- /dev/null +++ b/tests/servers/api/contract/mod.rs @@ -0,0 +1 @@ +pub mod stats; diff --git a/tests/servers/api/contract/stats/mod.rs b/tests/servers/api/contract/stats/mod.rs new file mode 100644 index 000000000..016a372dd --- /dev/null +++ b/tests/servers/api/contract/stats/mod.rs @@ -0,0 +1,94 @@ +use std::env; +use std::str::FromStr as _; + +use bittorrent_primitives::info_hash::InfoHash; +use bittorrent_tracker_client::http::client::requests::announce::QueryBuilder; +use bittorrent_tracker_client::http::client::Client as HttpTrackerClient; +use reqwest::Url; +use serde::Deserialize; +use tokio::time::Duration; +use torrust_rest_tracker_api_client::connection_info::{ConnectionInfo, Origin}; +use torrust_rest_tracker_api_client::v1::client::Client as TrackerApiClient; +use torrust_tracker_lib::app; + +#[tokio::test] +async fn the_stats_api_endpoint_should_return_the_global_stats() { + // Logging must be OFF otherwise your will get the following error: + // `Unable to install global subscriber: SetGlobalDefaultError("a global default trace dispatcher has already been set")` + // That's because we can't initialize the logger twice. + // You can enable it if you run only this test. + let config_with_two_http_trackers = r#" + [metadata] + app = "torrust-tracker" + purpose = "configuration" + schema_version = "2.0.0" + + [logging] + threshold = "off" + + [core] + listed = false + private = false + + [core.database] + driver = "sqlite3" + path = "./integration_tests_sqlite3.db" + + [[http_trackers]] + bind_address = "0.0.0.0:7272" + tracker_usage_statistics = true + + [[http_trackers]] + bind_address = "0.0.0.0:7373" + tracker_usage_statistics = true + + [http_api] + bind_address = "0.0.0.0:1414" + + [http_api.access_tokens] + admin = "MyAccessToken" + "#; + + env::set_var("TORRUST_TRACKER_CONFIG_TOML", config_with_two_http_trackers); + + let (_app_container, _jobs, _registar) = app::run().await; + + announce_to_tracker("http://127.0.0.1:7272").await; + announce_to_tracker("http://127.0.0.1:7373").await; + + let global_stats = get_tracker_statistics("http://127.0.0.1:1414", "MyAccessToken").await; + + assert_eq!(global_stats.tcp4_announces_handled, 2); +} + +/// Make a sample announce request to the tracker. +async fn announce_to_tracker(tracker_url: &str) { + let response = HttpTrackerClient::new(Url::parse(tracker_url).unwrap(), Duration::from_secs(1)) + .unwrap() + .announce( + &QueryBuilder::with_default_values() + .with_info_hash(&InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap()) // DevSkim: ignore DS173237 + .query(), + ) + .await; + + assert!(response.is_ok()); +} + +/// Global statistics with only metrics relevant to the test. +#[derive(Deserialize)] +struct PartialGlobalStatistics { + tcp4_announces_handled: u64, +} + +async fn get_tracker_statistics(aip_url: &str, token: &str) -> PartialGlobalStatistics { + let response = TrackerApiClient::new(ConnectionInfo::authenticated(Origin::new(aip_url).unwrap(), token)) + .unwrap() + .get_tracker_statistics(None) + .await; + + response + .json::() + .await + .expect("Failed to parse JSON response") +} diff --git a/tests/servers/api/mod.rs b/tests/servers/api/mod.rs new file mode 100644 index 000000000..2943dbb50 --- /dev/null +++ b/tests/servers/api/mod.rs @@ -0,0 +1 @@ +pub mod contract; diff --git a/tests/servers/health_check_api.rs b/tests/servers/health_check_api.rs deleted file mode 100644 index 0e66014da..000000000 --- a/tests/servers/health_check_api.rs +++ /dev/null @@ -1,32 +0,0 @@ -use reqwest::Response; -use torrust_axum_health_check_api_server::environment::Started; -use torrust_axum_health_check_api_server::resources::{Report, Status}; -use torrust_server_lib::registar::Registar; -use torrust_tracker_test_helpers::{configuration, logging}; - -pub async fn get(path: &str) -> Response { - reqwest::Client::builder().build().unwrap().get(path).send().await.unwrap() -} - -#[tokio::test] -async fn the_health_check_endpoint_should_return_status_ok_when_there_is_not_any_service_registered() { - logging::setup(); - - let configuration = configuration::ephemeral_with_no_services(); - - let env = Started::new(&configuration.health_check_api.into(), Registar::default()).await; - - let response = get(&format!("http://{}/health_check", env.state.binding)).await; // DevSkim: ignore DS137138 - - assert_eq!(response.status(), 200); - assert_eq!(response.headers().get("content-type").unwrap(), "application/json"); - - let report = response - .json::() - .await - .expect("it should be able to get the report as json"); - - assert_eq!(report.status, Status::None); - - env.stop().await.expect("it should stop the service"); -} diff --git a/tests/servers/mod.rs b/tests/servers/mod.rs index 7aeefeec4..e5fdf85ee 100644 --- a/tests/servers/mod.rs +++ b/tests/servers/mod.rs @@ -1 +1 @@ -pub mod health_check_api; +pub mod api;