From 568d6d362316e08fad7b7710fbb37d9534633687 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 7 Feb 2025 17:31:27 +0000 Subject: [PATCH 01/10] test: [#1251] add tests for core database driver sqlite --- .../src/databases/driver/sqlite.rs | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/packages/tracker-core/src/databases/driver/sqlite.rs b/packages/tracker-core/src/databases/driver/sqlite.rs index 3a08406fa..e04cf6110 100644 --- a/packages/tracker-core/src/databases/driver/sqlite.rs +++ b/packages/tracker-core/src/databases/driver/sqlite.rs @@ -288,3 +288,116 @@ impl Database for Sqlite { } } } + +#[cfg(test)] +mod tests { + + mod the_sqlite_driver { + use torrust_tracker_configuration::Core; + use torrust_tracker_test_helpers::configuration::ephemeral_sqlite_database; + + use crate::databases::driver::sqlite::Sqlite; + use crate::databases::Database; + + fn initialize_driver_and_database() -> Sqlite { + let config = ephemeral_configuration(); + let driver = Sqlite::new(&config.database.path).unwrap(); + driver.create_database_tables().unwrap(); + driver + } + + fn ephemeral_configuration() -> Core { + let mut config = Core::default(); + let temp_file = ephemeral_sqlite_database(); + temp_file.to_str().unwrap().clone_into(&mut config.database.path); + config + } + + mod handling_torrent_persistence { + + use crate::core_tests::sample_info_hash; + use crate::databases::driver::sqlite::tests::the_sqlite_driver::initialize_driver_and_database; + use crate::databases::Database; + + #[test] + fn it_should_save_and_load_persistent_torrents() { + let driver = initialize_driver_and_database(); + + let infohash = sample_info_hash(); + + let number_of_downloads = 1; + + driver.save_persistent_torrent(&infohash, number_of_downloads).unwrap(); + + let torrents = driver.load_persistent_torrents().unwrap(); + + assert_eq!(torrents.len(), 1); + assert_eq!(torrents.get(&infohash), Some(number_of_downloads).as_ref()); + } + } + + mod handling_authentication_keys { + use std::time::Duration; + + use crate::authentication::key::{generate_key, generate_permanent_key}; + use crate::databases::driver::sqlite::tests::the_sqlite_driver::initialize_driver_and_database; + use crate::databases::Database; + + #[test] + fn it_should_save_and_load_permanent_authentication_keys() { + let driver = initialize_driver_and_database(); + + // Add a new permanent key + let peer_key = generate_permanent_key(); + driver.add_key_to_keys(&peer_key).unwrap(); + + // Get the key back + let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); + + assert_eq!(stored_peer_key, peer_key); + } + #[test] + fn it_should_save_and_load_expiring_authentication_keys() { + let driver = initialize_driver_and_database(); + + // Add a new expiring key + let peer_key = generate_key(Some(Duration::from_secs(120))); + driver.add_key_to_keys(&peer_key).unwrap(); + + // Get the key back + let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); + + /* todo: + + The expiration time recovered from the database is not the same + as the one we set. It includes a small offset (nanoseconds). + + left: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308s) } + right: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308.603691299s) + + */ + + assert_eq!(stored_peer_key.key(), peer_key.key()); + assert_eq!( + stored_peer_key.valid_until.unwrap().as_secs(), + peer_key.valid_until.unwrap().as_secs() + ); + } + + #[test] + fn it_should_remove_an_authentication_key() { + let driver = initialize_driver_and_database(); + + let peer_key = generate_key(None); + + // Add a new key + driver.add_key_to_keys(&peer_key).unwrap(); + + // Remove the key + driver.remove_key_from_keys(&peer_key.key()).unwrap(); + + assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); + } + } + } +} From 46949a6fe1fcc3d4f7b877d0f5e2eebf89b65db0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 10 Feb 2025 17:27:01 +0000 Subject: [PATCH 02/10] test: [#1251] add tests for core database driver mysql --- .github/workflows/testing.yaml | 4 + Cargo.lock | 417 +++++++++++++++++- cSpell.json | 1 + packages/tracker-core/Cargo.toml | 11 +- .../src/databases/driver/mysql.rs | 245 ++++++++++ 5 files changed, 652 insertions(+), 26 deletions(-) diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index 28600dee9..671864fc9 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -146,6 +146,10 @@ jobs: name: Run Unit Tests run: cargo test --tests --benches --examples --workspace --all-targets --all-features + - id: database + name: Run MySQL Database Tests + run: TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true cargo test --package bittorrent-tracker-core + e2e: name: E2E runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index b186f0e9b..2f99db113 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,6 +314,17 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +[[package]] +name = "async-trait" +version = "0.1.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "atomic" version = "0.6.0" @@ -451,11 +462,11 @@ dependencies = [ "hyper", "hyper-util", "pin-project-lite", - "rustls", + "rustls 0.23.22", "rustls-pemfile", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.1", "tower 0.4.13", "tower-service", ] @@ -472,7 +483,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -608,6 +619,7 @@ dependencies = [ "rand 0.9.0", "serde", "serde_json", + "testcontainers", "thiserror 2.0.11", "tokio", "torrust-tracker-api-client", @@ -618,6 +630,7 @@ dependencies = [ "torrust-tracker-test-helpers", "torrust-tracker-torrent-repository", "tracing", + "url", ] [[package]] @@ -673,6 +686,56 @@ dependencies = [ "cipher", ] +[[package]] +name = "bollard" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aed08d3adb6ebe0eff737115056652670ae290f177759aac19c30456135f94c" +dependencies = [ + "base64 0.22.1", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-rustls 0.26.0", + "hyper-util", + "hyperlocal-next", + "log", + "pin-project-lite", + "rustls 0.22.4", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.44.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709d9aa1c37abb89d40f19f5d0ad6f0d88cb1581264e571c9350fc5bb89cf1c5" +dependencies = [ + "serde", + "serde_repr", + "serde_with", +] + [[package]] name = "borsh" version = "1.5.5" @@ -844,7 +907,7 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1227,6 +1290,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1238,6 +1322,17 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "docker_credential" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31951f49556e34d90ed28342e1df7e1cb7a229c4cab0aecc627b5d91edd41d07" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "downcast" version = "0.11.0" @@ -1609,7 +1704,7 @@ dependencies = [ "cfg-if", "libc", "wasi 0.13.3+wasi-0.2.2", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1724,6 +1819,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "1.2.0" @@ -1791,6 +1895,40 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "log", + "rustls 0.22.4", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", +] + [[package]] name = "hyper-rustls" version = "0.27.5" @@ -1801,10 +1939,10 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls", + "rustls 0.23.22", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.1", "tower-service", ] @@ -1843,6 +1981,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "hyperlocal-next" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf569d43fa9848e510358c07b80f4adf34084ddc28c6a4a651ee8474c070dcc" +dependencies = [ + "hex", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -2151,7 +2304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2160,6 +2313,16 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "libsqlite3-sys" version = "0.31.0" @@ -2571,6 +2734,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "overload" version = "0.1.1" @@ -2603,7 +2772,32 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn 2.0.98", ] [[package]] @@ -3043,6 +3237,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "regex" version = "1.11.1" @@ -3103,7 +3308,7 @@ dependencies = [ "http-body", "http-body-util", "hyper", - "hyper-rustls", + "hyper-rustls 0.27.5", "hyper-tls", "hyper-util", "ipnet", @@ -3279,6 +3484,20 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls" version = "0.23.22" @@ -3292,6 +3511,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -3648,6 +3880,29 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.98", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "subprocess" version = "0.2.9" @@ -3779,6 +4034,32 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "testcontainers" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "025e0ac563d543e0354d984540e749859a83dbe5c0afb8d458dc48d91cef2d6a" +dependencies = [ + "async-trait", + "bollard", + "bollard-stubs", + "bytes", + "dirs", + "docker_credential", + "futures", + "log", + "memchr", + "parse-display", + "serde", + "serde_json", + "serde_with", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "url", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -3933,13 +4214,35 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls", + "rustls 0.23.22", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", "tokio", ] @@ -4580,7 +4883,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -4591,7 +4894,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -4600,7 +4903,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -4610,7 +4913,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -4619,7 +4931,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -4628,7 +4940,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -4637,28 +4964,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4671,24 +5016,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/cSpell.json b/cSpell.json index a21e69b9f..b1e9a5e95 100644 --- a/cSpell.json +++ b/cSpell.json @@ -155,6 +155,7 @@ "taiki", "tdyne", "tempfile", + "testcontainers", "thiserror", "tlsv", "Torrentstorm", diff --git a/packages/tracker-core/Cargo.toml b/packages/tracker-core/Cargo.toml index 96505a7ba..46807a534 100644 --- a/packages/tracker-core/Cargo.toml +++ b/packages/tracker-core/Cargo.toml @@ -3,7 +3,6 @@ description = "A library with the core functionality needed to implement a BitTo keywords = ["api", "bittorrent", "core", "library", "tracker"] name = "bittorrent-tracker-core" readme = "README.md" - authors.workspace = true documentation.workspace = true edition.workspace = true @@ -27,7 +26,13 @@ rand = "0" serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["preserve_order"] } thiserror = "2" -tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal", "sync"] } +tokio = { version = "1", features = [ + "macros", + "net", + "rt-multi-thread", + "signal", + "sync", +] } torrust-tracker-clock = { version = "3.0.0-develop", path = "../clock" } torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" } torrust-tracker-located-error = { version = "3.0.0-develop", path = "../located-error" } @@ -40,3 +45,5 @@ local-ip-address = "0" mockall = "0" torrust-tracker-api-client = { version = "3.0.0-develop", path = "../tracker-api-client" } torrust-tracker-test-helpers = { version = "3.0.0-develop", path = "../test-helpers" } +testcontainers = "0.17.0" +url = "2.5.4" diff --git a/packages/tracker-core/src/databases/driver/mysql.rs b/packages/tracker-core/src/databases/driver/mysql.rs index 69fa1240e..a30d75b90 100644 --- a/packages/tracker-core/src/databases/driver/mysql.rs +++ b/packages/tracker-core/src/databases/driver/mysql.rs @@ -252,3 +252,248 @@ impl Database for Mysql { Ok(1) } } + +#[cfg(test)] +mod tests { + /* + We run a MySQL container and run all the tests against the same container and database. + + The `Database`` trait is very simple and we only have one driver that needs + a container. In the future we might want to use different approaches like: + + - https://github.com/testcontainers/testcontainers-rs/issues/707 + - https://www.infinyon.com/blog/2021/04/rust-custom-test-harness/ + - https://github.com/torrust/torrust-tracker/blob/develop/src/bin/e2e_tests_runner.rs + + If we increase the number of methods or the number or drivers. + */ + use std::time::Duration; + + use testcontainers::runners::AsyncRunner; + use testcontainers::{ContainerAsync, GenericImage}; + use torrust_tracker_configuration::Core; + + use super::Mysql; + use crate::databases::Database; + + #[derive(Debug, Default)] + struct StoppedMysqlContainer {} + + impl StoppedMysqlContainer { + async fn run(self, config: &MysqlConfiguration) -> Result> { + let container = GenericImage::new("mysql", "8.0") + .with_env_var("MYSQL_ROOT_PASSWORD", config.db_root_password.clone()) + .with_env_var("MYSQL_DATABASE", config.database.clone()) + .with_env_var("MYSQL_ROOT_HOST", "%") + .with_exposed_port(config.internal_port) + // todo: this doesn't work + //.with_wait_for(WaitFor::message_on_stdout("ready for connections")) + .start() + .await?; + + Ok(RunningMysqlContainer::new(container, config.internal_port)) + } + } + + struct RunningMysqlContainer { + container: ContainerAsync, + internal_port: u16, + } + + impl RunningMysqlContainer { + fn new(container: ContainerAsync, internal_port: u16) -> Self { + Self { + container, + internal_port, + } + } + + async fn stop(self) { + self.container.stop().await.unwrap(); + } + + async fn get_host(&self) -> url::Host { + self.container.get_host().await.unwrap() + } + + async fn get_host_port_ipv4(&self) -> u16 { + self.container.get_host_port_ipv4(self.internal_port).await.unwrap() + } + } + + impl Default for MysqlConfiguration { + fn default() -> Self { + Self { + internal_port: 3306, + database: "torrust_tracker_test".to_string(), + db_user: "root".to_string(), + db_root_password: "test".to_string(), + } + } + } + + struct MysqlConfiguration { + pub internal_port: u16, + pub database: String, + pub db_user: String, + pub db_root_password: String, + } + + fn core_configuration(host: &url::Host, port: u16, mysql_configuration: &MysqlConfiguration) -> Core { + let mut config = Core::default(); + + let database = mysql_configuration.database.clone(); + let db_user = mysql_configuration.db_user.clone(); + let db_password = mysql_configuration.db_root_password.clone(); + + config.database.path = format!("mysql://{db_user}:{db_password}@{host}:{port}/{database}"); + + config + } + + fn initialize_driver(config: &Core) -> Mysql { + Mysql::new(&config.database.path).unwrap() + } + + async fn create_database_tables(driver: &Mysql) -> Result<(), Box> { + for _ in 0..5 { + if driver.create_database_tables().is_ok() { + return Ok(()); + } + tokio::time::sleep(Duration::from_secs(2)).await; + } + Err("MySQL is not ready after retries.".into()) + } + + #[tokio::test] + async fn run() -> Result<(), Box> { + if std::env::var("TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST").is_err() { + println!("Skipping the MySQL driver tests."); + return Ok(()); + } + + let mysql_configuration = MysqlConfiguration::default(); + + let stopped_mysql_container = StoppedMysqlContainer::default(); + + let mysql_container = stopped_mysql_container.run(&mysql_configuration).await.unwrap(); + + let host = mysql_container.get_host().await; + let port = mysql_container.get_host_port_ipv4().await; + + let config = core_configuration(&host, port, &mysql_configuration); + + let driver = initialize_driver(&config); + + // Since the interface is very simple and there are no conflicts between + // tests, we share the same database. If we want to isolate the tests in + // the future, we can create a new database for each test. + create_database_tables(&driver).await?; + + // todo: truncate tables otherwise they will increase in size over time. + // That's not a problem on CI when the database is always newly created. + + handling_torrent_persistence::it_should_save_and_load_persistent_torrents(&driver); + + // Permanent keys + //handling_authentication_keys::it_should_save_and_load_permanent_authentication_keys(&driver); + //handling_authentication_keys::it_should_remove_a_permanent_authentication_key(&driver); + + // Expiring keys + handling_authentication_keys::it_should_save_and_load_expiring_authentication_keys(&driver); + //handling_authentication_keys::it_should_remove_an_expiring_authentication_key(&driver); + + driver.drop_database_tables().unwrap(); + + mysql_container.stop().await; + + Ok(()) + } + + mod handling_torrent_persistence { + + use crate::core_tests::sample_info_hash; + use crate::databases::Database; + + pub fn it_should_save_and_load_persistent_torrents(driver: &impl Database) { + let infohash = sample_info_hash(); + + let number_of_downloads = 1; + + driver.save_persistent_torrent(&infohash, number_of_downloads).unwrap(); + + let torrents = driver.load_persistent_torrents().unwrap(); + + assert_eq!(torrents.len(), 1); + assert_eq!(torrents.get(&infohash), Some(number_of_downloads).as_ref()); + } + } + + mod handling_authentication_keys { + + use std::time::Duration; + + use crate::authentication::key::generate_key; + use crate::databases::Database; + + /*pub fn it_should_save_and_load_permanent_authentication_keys(driver: &impl Database) { + // Add a new permanent key + let peer_key = generate_permanent_key(); + driver.add_key_to_keys(&peer_key).unwrap(); + + // Get the key back + let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); + + assert_eq!(stored_peer_key, peer_key); + }*/ + + pub fn it_should_save_and_load_expiring_authentication_keys(driver: &impl Database) { + // Add a new expiring key + let peer_key = generate_key(Some(Duration::from_secs(120))); + driver.add_key_to_keys(&peer_key).unwrap(); + + // Get the key back + let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); + + /* todo: + + The expiration time recovered from the database is not the same + as the one we set. It includes a small offset (nanoseconds). + + left: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308s) } + right: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308.603691299s) + + */ + + assert_eq!(stored_peer_key.key(), peer_key.key()); + assert_eq!( + stored_peer_key.valid_until.unwrap().as_secs(), + peer_key.valid_until.unwrap().as_secs() + ); + } + + /*pub fn it_should_remove_a_permanent_authentication_key(driver: &impl Database) { + let peer_key = generate_permanent_key(); + + // Add a new key + driver.add_key_to_keys(&peer_key).unwrap(); + + // Remove the key + driver.remove_key_from_keys(&peer_key.key()).unwrap(); + + assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); + }*/ + + /*pub fn it_should_remove_an_expiring_authentication_key(driver: &impl Database) { + let peer_key = generate_key(Some(Duration::from_secs(120))); + + // Add a new key + driver.add_key_to_keys(&peer_key).unwrap(); + + // Remove the key + driver.remove_key_from_keys(&peer_key.key()).unwrap(); + + assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); + }*/ + } +} From 7ddacdc73d51a3365ce8068c9c8298a853e3b24d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 10 Feb 2025 19:02:13 +0000 Subject: [PATCH 03/10] test: [#1251] unify test runner for sqlite and mysql drivers In the tracker-core package. --- packages/tracker-core/README.md | 8 ++ .../tracker-core/src/databases/driver/mod.rs | 130 +++++++++++++++++ .../src/databases/driver/mysql.rs | 133 ++---------------- .../src/databases/driver/sqlite.rs | 121 +++------------- 4 files changed, 172 insertions(+), 220 deletions(-) diff --git a/packages/tracker-core/README.md b/packages/tracker-core/README.md index e36a6f4be..dfb57f304 100644 --- a/packages/tracker-core/README.md +++ b/packages/tracker-core/README.md @@ -12,6 +12,14 @@ You usually don’t need to use this library directly. Instead, you should use t ## Testing +Run tests including tests for MySQL driver: + +```console +TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true cargo test +``` + +> NOTE: MySQL driver requires docker to run. We don't run them by default because we don't want to run them when we build container images. The Torrust Tracker container build runs unit tests for all dependencies, including this library. + Show coverage report: ```console diff --git a/packages/tracker-core/src/databases/driver/mod.rs b/packages/tracker-core/src/databases/driver/mod.rs index bdef7fcee..30888375e 100644 --- a/packages/tracker-core/src/databases/driver/mod.rs +++ b/packages/tracker-core/src/databases/driver/mod.rs @@ -83,3 +83,133 @@ pub fn build(driver: &Driver, db_path: &str) -> Result, Error> Ok(database) } + +#[cfg(test)] +mod tests { + use std::sync::Arc; + use std::time::Duration; + + use crate::databases::Database; + + pub async fn run_tests(driver: &Arc>) { + // Since the interface is very simple and there are no conflicts between + // tests, we share the same database. If we want to isolate the tests in + // the future, we can create a new database for each test. + create_database_tables(driver).await.unwrap(); + + // todo: truncate tables otherwise they will increase in size over time. + // That's not a problem on CI when the database is always newly created. + + handling_torrent_persistence::it_should_save_and_load_persistent_torrents(driver); + + // Permanent keys + //handling_authentication_keys::it_should_save_and_load_permanent_authentication_keys(&driver); + //handling_authentication_keys::it_should_remove_a_permanent_authentication_key(&driver); + + // Expiring keys + handling_authentication_keys::it_should_save_and_load_expiring_authentication_keys(driver); + //handling_authentication_keys::it_should_remove_an_expiring_authentication_key(&driver); + + driver.drop_database_tables().unwrap(); + } + + async fn create_database_tables(driver: &Arc>) -> Result<(), Box> { + for _ in 0..5 { + if driver.create_database_tables().is_ok() { + return Ok(()); + } + tokio::time::sleep(Duration::from_secs(2)).await; + } + Err("MySQL is not ready after retries.".into()) + } + + mod handling_torrent_persistence { + + use std::sync::Arc; + + use crate::core_tests::sample_info_hash; + use crate::databases::Database; + + pub fn it_should_save_and_load_persistent_torrents(driver: &Arc>) { + let infohash = sample_info_hash(); + + let number_of_downloads = 1; + + driver.save_persistent_torrent(&infohash, number_of_downloads).unwrap(); + + let torrents = driver.load_persistent_torrents().unwrap(); + + assert_eq!(torrents.len(), 1); + assert_eq!(torrents.get(&infohash), Some(number_of_downloads).as_ref()); + } + } + + mod handling_authentication_keys { + + use std::sync::Arc; + use std::time::Duration; + + use crate::authentication::key::generate_key; + use crate::databases::Database; + + /*pub fn it_should_save_and_load_permanent_authentication_keys(driver: &Arc>) { + // Add a new permanent key + let peer_key = generate_permanent_key(); + driver.add_key_to_keys(&peer_key).unwrap(); + + // Get the key back + let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); + + assert_eq!(stored_peer_key, peer_key); + }*/ + + pub fn it_should_save_and_load_expiring_authentication_keys(driver: &Arc>) { + // Add a new expiring key + let peer_key = generate_key(Some(Duration::from_secs(120))); + driver.add_key_to_keys(&peer_key).unwrap(); + + // Get the key back + let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); + + /* todo: + + The expiration time recovered from the database is not the same + as the one we set. It includes a small offset (nanoseconds). + + left: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308s) } + right: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308.603691299s) + + */ + + assert_eq!(stored_peer_key.key(), peer_key.key()); + assert_eq!( + stored_peer_key.valid_until.unwrap().as_secs(), + peer_key.valid_until.unwrap().as_secs() + ); + } + + /*pub fn it_should_remove_a_permanent_authentication_key(driver: &Arc>) { + let peer_key = generate_permanent_key(); + + // Add a new key + driver.add_key_to_keys(&peer_key).unwrap(); + + // Remove the key + driver.remove_key_from_keys(&peer_key.key()).unwrap(); + + assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); + }*/ + + /*pub fn it_should_remove_an_expiring_authentication_key(driver: &Arc>) { + let peer_key = generate_key(Some(Duration::from_secs(120))); + + // Add a new key + driver.add_key_to_keys(&peer_key).unwrap(); + + // Remove the key + driver.remove_key_from_keys(&peer_key.key()).unwrap(); + + assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); + }*/ + } +} diff --git a/packages/tracker-core/src/databases/driver/mysql.rs b/packages/tracker-core/src/databases/driver/mysql.rs index a30d75b90..39ef8f55b 100644 --- a/packages/tracker-core/src/databases/driver/mysql.rs +++ b/packages/tracker-core/src/databases/driver/mysql.rs @@ -255,9 +255,15 @@ impl Database for Mysql { #[cfg(test)] mod tests { + use std::sync::Arc; + /* We run a MySQL container and run all the tests against the same container and database. + Test for this driver are executed with: + + `TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true cargo test` + The `Database`` trait is very simple and we only have one driver that needs a container. In the future we might want to use different approaches like: @@ -267,13 +273,12 @@ mod tests { If we increase the number of methods or the number or drivers. */ - use std::time::Duration; - use testcontainers::runners::AsyncRunner; use testcontainers::{ContainerAsync, GenericImage}; use torrust_tracker_configuration::Core; use super::Mysql; + use crate::databases::driver::tests::run_tests; use crate::databases::Database; #[derive(Debug, Default)] @@ -351,22 +356,13 @@ mod tests { config } - fn initialize_driver(config: &Core) -> Mysql { - Mysql::new(&config.database.path).unwrap() - } - - async fn create_database_tables(driver: &Mysql) -> Result<(), Box> { - for _ in 0..5 { - if driver.create_database_tables().is_ok() { - return Ok(()); - } - tokio::time::sleep(Duration::from_secs(2)).await; - } - Err("MySQL is not ready after retries.".into()) + fn initialize_driver(config: &Core) -> Arc> { + let driver: Arc> = Arc::new(Box::new(Mysql::new(&config.database.path).unwrap())); + driver } #[tokio::test] - async fn run() -> Result<(), Box> { + async fn run_mysql_driver_tests() -> Result<(), Box> { if std::env::var("TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST").is_err() { println!("Skipping the MySQL driver tests."); return Ok(()); @@ -385,115 +381,10 @@ mod tests { let driver = initialize_driver(&config); - // Since the interface is very simple and there are no conflicts between - // tests, we share the same database. If we want to isolate the tests in - // the future, we can create a new database for each test. - create_database_tables(&driver).await?; - - // todo: truncate tables otherwise they will increase in size over time. - // That's not a problem on CI when the database is always newly created. - - handling_torrent_persistence::it_should_save_and_load_persistent_torrents(&driver); - - // Permanent keys - //handling_authentication_keys::it_should_save_and_load_permanent_authentication_keys(&driver); - //handling_authentication_keys::it_should_remove_a_permanent_authentication_key(&driver); - - // Expiring keys - handling_authentication_keys::it_should_save_and_load_expiring_authentication_keys(&driver); - //handling_authentication_keys::it_should_remove_an_expiring_authentication_key(&driver); - - driver.drop_database_tables().unwrap(); + run_tests(&driver).await; mysql_container.stop().await; Ok(()) } - - mod handling_torrent_persistence { - - use crate::core_tests::sample_info_hash; - use crate::databases::Database; - - pub fn it_should_save_and_load_persistent_torrents(driver: &impl Database) { - let infohash = sample_info_hash(); - - let number_of_downloads = 1; - - driver.save_persistent_torrent(&infohash, number_of_downloads).unwrap(); - - let torrents = driver.load_persistent_torrents().unwrap(); - - assert_eq!(torrents.len(), 1); - assert_eq!(torrents.get(&infohash), Some(number_of_downloads).as_ref()); - } - } - - mod handling_authentication_keys { - - use std::time::Duration; - - use crate::authentication::key::generate_key; - use crate::databases::Database; - - /*pub fn it_should_save_and_load_permanent_authentication_keys(driver: &impl Database) { - // Add a new permanent key - let peer_key = generate_permanent_key(); - driver.add_key_to_keys(&peer_key).unwrap(); - - // Get the key back - let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); - - assert_eq!(stored_peer_key, peer_key); - }*/ - - pub fn it_should_save_and_load_expiring_authentication_keys(driver: &impl Database) { - // Add a new expiring key - let peer_key = generate_key(Some(Duration::from_secs(120))); - driver.add_key_to_keys(&peer_key).unwrap(); - - // Get the key back - let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); - - /* todo: - - The expiration time recovered from the database is not the same - as the one we set. It includes a small offset (nanoseconds). - - left: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308s) } - right: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308.603691299s) - - */ - - assert_eq!(stored_peer_key.key(), peer_key.key()); - assert_eq!( - stored_peer_key.valid_until.unwrap().as_secs(), - peer_key.valid_until.unwrap().as_secs() - ); - } - - /*pub fn it_should_remove_a_permanent_authentication_key(driver: &impl Database) { - let peer_key = generate_permanent_key(); - - // Add a new key - driver.add_key_to_keys(&peer_key).unwrap(); - - // Remove the key - driver.remove_key_from_keys(&peer_key.key()).unwrap(); - - assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); - }*/ - - /*pub fn it_should_remove_an_expiring_authentication_key(driver: &impl Database) { - let peer_key = generate_key(Some(Duration::from_secs(120))); - - // Add a new key - driver.add_key_to_keys(&peer_key).unwrap(); - - // Remove the key - driver.remove_key_from_keys(&peer_key.key()).unwrap(); - - assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); - }*/ - } } diff --git a/packages/tracker-core/src/databases/driver/sqlite.rs b/packages/tracker-core/src/databases/driver/sqlite.rs index e04cf6110..37f5254a5 100644 --- a/packages/tracker-core/src/databases/driver/sqlite.rs +++ b/packages/tracker-core/src/databases/driver/sqlite.rs @@ -292,112 +292,35 @@ impl Database for Sqlite { #[cfg(test)] mod tests { - mod the_sqlite_driver { - use torrust_tracker_configuration::Core; - use torrust_tracker_test_helpers::configuration::ephemeral_sqlite_database; - - use crate::databases::driver::sqlite::Sqlite; - use crate::databases::Database; - - fn initialize_driver_and_database() -> Sqlite { - let config = ephemeral_configuration(); - let driver = Sqlite::new(&config.database.path).unwrap(); - driver.create_database_tables().unwrap(); - driver - } - - fn ephemeral_configuration() -> Core { - let mut config = Core::default(); - let temp_file = ephemeral_sqlite_database(); - temp_file.to_str().unwrap().clone_into(&mut config.database.path); - config - } - - mod handling_torrent_persistence { - - use crate::core_tests::sample_info_hash; - use crate::databases::driver::sqlite::tests::the_sqlite_driver::initialize_driver_and_database; - use crate::databases::Database; - - #[test] - fn it_should_save_and_load_persistent_torrents() { - let driver = initialize_driver_and_database(); - - let infohash = sample_info_hash(); - - let number_of_downloads = 1; - - driver.save_persistent_torrent(&infohash, number_of_downloads).unwrap(); - - let torrents = driver.load_persistent_torrents().unwrap(); - - assert_eq!(torrents.len(), 1); - assert_eq!(torrents.get(&infohash), Some(number_of_downloads).as_ref()); - } - } - - mod handling_authentication_keys { - use std::time::Duration; - - use crate::authentication::key::{generate_key, generate_permanent_key}; - use crate::databases::driver::sqlite::tests::the_sqlite_driver::initialize_driver_and_database; - use crate::databases::Database; + use std::sync::Arc; - #[test] - fn it_should_save_and_load_permanent_authentication_keys() { - let driver = initialize_driver_and_database(); + use torrust_tracker_configuration::Core; + use torrust_tracker_test_helpers::configuration::ephemeral_sqlite_database; - // Add a new permanent key - let peer_key = generate_permanent_key(); - driver.add_key_to_keys(&peer_key).unwrap(); + use crate::databases::driver::sqlite::Sqlite; + use crate::databases::driver::tests::run_tests; + use crate::databases::Database; - // Get the key back - let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); - - assert_eq!(stored_peer_key, peer_key); - } - #[test] - fn it_should_save_and_load_expiring_authentication_keys() { - let driver = initialize_driver_and_database(); - - // Add a new expiring key - let peer_key = generate_key(Some(Duration::from_secs(120))); - driver.add_key_to_keys(&peer_key).unwrap(); - - // Get the key back - let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); - - /* todo: - - The expiration time recovered from the database is not the same - as the one we set. It includes a small offset (nanoseconds). - - left: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308s) } - right: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308.603691299s) - - */ - - assert_eq!(stored_peer_key.key(), peer_key.key()); - assert_eq!( - stored_peer_key.valid_until.unwrap().as_secs(), - peer_key.valid_until.unwrap().as_secs() - ); - } + fn ephemeral_configuration() -> Core { + let mut config = Core::default(); + let temp_file = ephemeral_sqlite_database(); + temp_file.to_str().unwrap().clone_into(&mut config.database.path); + config + } - #[test] - fn it_should_remove_an_authentication_key() { - let driver = initialize_driver_and_database(); + fn initialize_driver(config: &Core) -> Arc> { + let driver: Arc> = Arc::new(Box::new(Sqlite::new(&config.database.path).unwrap())); + driver + } - let peer_key = generate_key(None); + #[tokio::test] + async fn run_sqlite_driver_tests() -> Result<(), Box> { + let config = ephemeral_configuration(); - // Add a new key - driver.add_key_to_keys(&peer_key).unwrap(); + let driver = initialize_driver(&config); - // Remove the key - driver.remove_key_from_keys(&peer_key.key()).unwrap(); + run_tests(&driver).await; - assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); - } - } + Ok(()) } } From 595397b74d0542900ca61c585420092c86d8aee2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 10 Feb 2025 19:36:04 +0000 Subject: [PATCH 04/10] fix: [#1257] bug 1. permanent keys can't be created in MySQL --- .../tracker-core/src/databases/driver/mod.rs | 9 +++++---- .../src/databases/driver/mysql.rs | 20 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/tracker-core/src/databases/driver/mod.rs b/packages/tracker-core/src/databases/driver/mod.rs index 30888375e..7b42475a6 100644 --- a/packages/tracker-core/src/databases/driver/mod.rs +++ b/packages/tracker-core/src/databases/driver/mod.rs @@ -95,6 +95,7 @@ mod tests { // Since the interface is very simple and there are no conflicts between // tests, we share the same database. If we want to isolate the tests in // the future, we can create a new database for each test. + create_database_tables(driver).await.unwrap(); // todo: truncate tables otherwise they will increase in size over time. @@ -103,7 +104,7 @@ mod tests { handling_torrent_persistence::it_should_save_and_load_persistent_torrents(driver); // Permanent keys - //handling_authentication_keys::it_should_save_and_load_permanent_authentication_keys(&driver); + handling_authentication_keys::it_should_save_and_load_permanent_authentication_keys(driver); //handling_authentication_keys::it_should_remove_a_permanent_authentication_key(&driver); // Expiring keys @@ -149,10 +150,10 @@ mod tests { use std::sync::Arc; use std::time::Duration; - use crate::authentication::key::generate_key; + use crate::authentication::key::{generate_key, generate_permanent_key}; use crate::databases::Database; - /*pub fn it_should_save_and_load_permanent_authentication_keys(driver: &Arc>) { + pub fn it_should_save_and_load_permanent_authentication_keys(driver: &Arc>) { // Add a new permanent key let peer_key = generate_permanent_key(); driver.add_key_to_keys(&peer_key).unwrap(); @@ -161,7 +162,7 @@ mod tests { let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); assert_eq!(stored_peer_key, peer_key); - }*/ + } pub fn it_should_save_and_load_expiring_authentication_keys(driver: &Arc>) { // Add a new expiring key diff --git a/packages/tracker-core/src/databases/driver/mysql.rs b/packages/tracker-core/src/databases/driver/mysql.rs index 39ef8f55b..976a26d49 100644 --- a/packages/tracker-core/src/databases/driver/mysql.rs +++ b/packages/tracker-core/src/databases/driver/mysql.rs @@ -229,16 +229,16 @@ impl Database for Mysql { fn add_key_to_keys(&self, auth_key: &authentication::PeerKey) -> Result { let mut conn = self.pool.get().map_err(|e| (e, DRIVER))?; - let key = auth_key.key.to_string(); - let valid_until = match auth_key.valid_until { - Some(valid_until) => valid_until.as_secs().to_string(), - None => todo!(), - }; - - conn.exec_drop( - "INSERT INTO `keys` (`key`, valid_until) VALUES (:key, :valid_until)", - params! { key, valid_until }, - )?; + match auth_key.valid_until { + Some(valid_until) => conn.exec_drop( + "INSERT INTO `keys` (`key`, valid_until) VALUES (:key, :valid_until)", + params! { "key" => auth_key.key.to_string(), "valid_until" => valid_until.as_secs().to_string() }, + )?, + None => conn.exec_drop( + "INSERT INTO `keys` (`key`) VALUES (:key)", + params! { "key" => auth_key.key.to_string() }, + )?, + } Ok(1) } From b94179dd1f742fd429b6765e3a04936cc3373331 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 10 Feb 2025 19:41:03 +0000 Subject: [PATCH 05/10] fix: [#1257] bug 2. Auth keys can't be removed in MySQL --- packages/tracker-core/README.md | 4 ++-- .../tracker-core/src/databases/driver/mod.rs | 20 +++++++++++++------ .../src/databases/driver/mysql.rs | 2 +- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/tracker-core/README.md b/packages/tracker-core/README.md index dfb57f304..f80243d29 100644 --- a/packages/tracker-core/README.md +++ b/packages/tracker-core/README.md @@ -23,13 +23,13 @@ TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true cargo test Show coverage report: ```console -cargo +stable llvm-cov +TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true cargo +stable llvm-cov ``` Export coverage report to `lcov` format: ```console -cargo +stable llvm-cov --lcov --output-path=./.coverage/lcov.info +TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true cargo +stable llvm-cov --lcov --output-path=./.coverage/lcov.info ``` If you use Visual Studio Code, you can use the [Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=semasquare.vscode-coverage-gutters) extension to view the coverage lines. diff --git a/packages/tracker-core/src/databases/driver/mod.rs b/packages/tracker-core/src/databases/driver/mod.rs index 7b42475a6..1f0b54a57 100644 --- a/packages/tracker-core/src/databases/driver/mod.rs +++ b/packages/tracker-core/src/databases/driver/mod.rs @@ -101,15 +101,23 @@ mod tests { // todo: truncate tables otherwise they will increase in size over time. // That's not a problem on CI when the database is always newly created. + // Persistent torrents (stats) + handling_torrent_persistence::it_should_save_and_load_persistent_torrents(driver); + // Authentication keys (for private trackers) + // Permanent keys handling_authentication_keys::it_should_save_and_load_permanent_authentication_keys(driver); - //handling_authentication_keys::it_should_remove_a_permanent_authentication_key(&driver); + handling_authentication_keys::it_should_remove_a_permanent_authentication_key(driver); // Expiring keys handling_authentication_keys::it_should_save_and_load_expiring_authentication_keys(driver); - //handling_authentication_keys::it_should_remove_an_expiring_authentication_key(&driver); + handling_authentication_keys::it_should_remove_an_expiring_authentication_key(driver); + + // Whitelist (for listed trackers) + + // todo driver.drop_database_tables().unwrap(); } @@ -189,7 +197,7 @@ mod tests { ); } - /*pub fn it_should_remove_a_permanent_authentication_key(driver: &Arc>) { + pub fn it_should_remove_a_permanent_authentication_key(driver: &Arc>) { let peer_key = generate_permanent_key(); // Add a new key @@ -199,9 +207,9 @@ mod tests { driver.remove_key_from_keys(&peer_key.key()).unwrap(); assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); - }*/ + } - /*pub fn it_should_remove_an_expiring_authentication_key(driver: &Arc>) { + pub fn it_should_remove_an_expiring_authentication_key(driver: &Arc>) { let peer_key = generate_key(Some(Duration::from_secs(120))); // Add a new key @@ -211,6 +219,6 @@ mod tests { driver.remove_key_from_keys(&peer_key.key()).unwrap(); assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); - }*/ + } } } diff --git a/packages/tracker-core/src/databases/driver/mysql.rs b/packages/tracker-core/src/databases/driver/mysql.rs index 976a26d49..a739832d6 100644 --- a/packages/tracker-core/src/databases/driver/mysql.rs +++ b/packages/tracker-core/src/databases/driver/mysql.rs @@ -247,7 +247,7 @@ impl Database for Mysql { fn remove_key_from_keys(&self, key: &Key) -> Result { let mut conn = self.pool.get().map_err(|e| (e, DRIVER))?; - conn.exec_drop("DELETE FROM `keys` WHERE key = :key", params! { "key" => key.to_string() })?; + conn.exec_drop("DELETE FROM `keys` WHERE `key` = :key", params! { "key" => key.to_string() })?; Ok(1) } From 613efb26b7f020169bceb03d86300dae2d24ed10 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 10 Feb 2025 20:30:19 +0000 Subject: [PATCH 06/10] fix: [#1257] bug 3. Expiring auth keys ignore fractions of seconds The `Duration` of a peer Key can have gractions of seconds. However we only store seconds (integer) in the database. When comparing peer keys we should ignore the fractions. --- .../src/authentication/key/peer_key.rs | 23 +++++++++++++++++-- .../tracker-core/src/databases/driver/mod.rs | 17 ++------------ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/tracker-core/src/authentication/key/peer_key.rs b/packages/tracker-core/src/authentication/key/peer_key.rs index a3045e54e..1d2b1fadc 100644 --- a/packages/tracker-core/src/authentication/key/peer_key.rs +++ b/packages/tracker-core/src/authentication/key/peer_key.rs @@ -1,4 +1,5 @@ use std::str::FromStr; +use std::time::Duration; use derive_more::Display; use rand::distr::Alphanumeric; @@ -12,7 +13,7 @@ use super::AUTH_KEY_LENGTH; /// An authentication key which can potentially have an expiration time. /// After that time is will automatically become invalid. -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct PeerKey { /// Random 32-char string. For example: `YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ` pub key: Key, @@ -22,6 +23,21 @@ pub struct PeerKey { pub valid_until: Option, } +impl PartialEq for PeerKey { + fn eq(&self, other: &Self) -> bool { + // We ignore the fractions of seconds when comparing the timestamps + // because we only store the seconds in the database. + self.key == other.key + && match (&self.valid_until, &other.valid_until) { + (Some(a), Some(b)) => a.as_secs() == b.as_secs(), + (None, None) => true, + _ => false, + } + } +} + +impl Eq for PeerKey {} + impl std::fmt::Display for PeerKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.expiry_time() { @@ -47,7 +63,10 @@ impl PeerKey { /// (this will naturally happen in 292.5 billion years) #[must_use] pub fn expiry_time(&self) -> Option> { - self.valid_until.map(convert_from_timestamp_to_datetime_utc) + // We remove the fractions of seconds because we only store the seconds + // in the database. + self.valid_until + .map(|valid_until| convert_from_timestamp_to_datetime_utc(Duration::from_secs(valid_until.as_secs()))) } } diff --git a/packages/tracker-core/src/databases/driver/mod.rs b/packages/tracker-core/src/databases/driver/mod.rs index 1f0b54a57..557633b81 100644 --- a/packages/tracker-core/src/databases/driver/mod.rs +++ b/packages/tracker-core/src/databases/driver/mod.rs @@ -180,21 +180,8 @@ mod tests { // Get the key back let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); - /* todo: - - The expiration time recovered from the database is not the same - as the one we set. It includes a small offset (nanoseconds). - - left: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308s) } - right: PeerKey { key: Key("7HP1NslpuQn6kLVAgAF4nFpnZNSQ4hrx"), valid_until: Some(1739182308.603691299s) - - */ - - assert_eq!(stored_peer_key.key(), peer_key.key()); - assert_eq!( - stored_peer_key.valid_until.unwrap().as_secs(), - peer_key.valid_until.unwrap().as_secs() - ); + assert_eq!(stored_peer_key, peer_key); + assert_eq!(stored_peer_key.expiry_time(), peer_key.expiry_time()); } pub fn it_should_remove_a_permanent_authentication_key(driver: &Arc>) { From b9188c74b9d2f08994ec5caf48bfba2d89b6a525 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 10 Feb 2025 20:50:53 +0000 Subject: [PATCH 07/10] test: [#1251] add tests for whitelist is DB drivers --- packages/tracker-core/src/core_tests.rs | 11 ++++ .../tracker-core/src/databases/driver/mod.rs | 62 +++++++++++++++---- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/packages/tracker-core/src/core_tests.rs b/packages/tracker-core/src/core_tests.rs index 53049f326..165c8790e 100644 --- a/packages/tracker-core/src/core_tests.rs +++ b/packages/tracker-core/src/core_tests.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; use bittorrent_primitives::info_hash::InfoHash; +use rand::Rng; use torrust_tracker_configuration::Configuration; #[cfg(test)] use torrust_tracker_configuration::Core; @@ -20,6 +21,16 @@ use super::torrent::repository::persisted::DatabasePersistentTorrentRepository; use super::whitelist::repository::in_memory::InMemoryWhitelist; use super::whitelist::{self}; +/// Generates a random `InfoHash`. +#[must_use] +pub fn random_info_hash() -> InfoHash { + let mut rng = rand::rng(); + let mut random_bytes = [0u8; 20]; + rng.fill(&mut random_bytes); + + InfoHash::from_bytes(&random_bytes) +} + /// # Panics /// /// Will panic if the string representation of the info hash is not a valid info hash. diff --git a/packages/tracker-core/src/databases/driver/mod.rs b/packages/tracker-core/src/databases/driver/mod.rs index 557633b81..f4e165f00 100644 --- a/packages/tracker-core/src/databases/driver/mod.rs +++ b/packages/tracker-core/src/databases/driver/mod.rs @@ -117,7 +117,10 @@ mod tests { // Whitelist (for listed trackers) - // todo + handling_the_whitelist::it_should_add_and_get_infohashes(driver); + handling_the_whitelist::it_should_remove_an_infohash_from_the_whitelist(driver); + handling_the_whitelist::it_should_fail_trying_to_add_the_same_infohash_twice(driver); + handling_the_whitelist::it_load_the_whitelist(driver); driver.drop_database_tables().unwrap(); } @@ -129,7 +132,7 @@ mod tests { } tokio::time::sleep(Duration::from_secs(2)).await; } - Err("MySQL is not ready after retries.".into()) + Err("Database is not ready after retries.".into()) } mod handling_torrent_persistence { @@ -162,22 +165,18 @@ mod tests { use crate::databases::Database; pub fn it_should_save_and_load_permanent_authentication_keys(driver: &Arc>) { - // Add a new permanent key let peer_key = generate_permanent_key(); driver.add_key_to_keys(&peer_key).unwrap(); - // Get the key back let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); assert_eq!(stored_peer_key, peer_key); } pub fn it_should_save_and_load_expiring_authentication_keys(driver: &Arc>) { - // Add a new expiring key let peer_key = generate_key(Some(Duration::from_secs(120))); driver.add_key_to_keys(&peer_key).unwrap(); - // Get the key back let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); assert_eq!(stored_peer_key, peer_key); @@ -186,11 +185,8 @@ mod tests { pub fn it_should_remove_a_permanent_authentication_key(driver: &Arc>) { let peer_key = generate_permanent_key(); - - // Add a new key driver.add_key_to_keys(&peer_key).unwrap(); - // Remove the key driver.remove_key_from_keys(&peer_key.key()).unwrap(); assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); @@ -198,14 +194,56 @@ mod tests { pub fn it_should_remove_an_expiring_authentication_key(driver: &Arc>) { let peer_key = generate_key(Some(Duration::from_secs(120))); - - // Add a new key driver.add_key_to_keys(&peer_key).unwrap(); - // Remove the key driver.remove_key_from_keys(&peer_key.key()).unwrap(); assert!(driver.get_key_from_keys(&peer_key.key()).unwrap().is_none()); } } + + mod handling_the_whitelist { + + use std::sync::Arc; + + use crate::core_tests::random_info_hash; + use crate::databases::Database; + + pub fn it_should_add_and_get_infohashes(driver: &Arc>) { + let infohash = random_info_hash(); + + driver.add_info_hash_to_whitelist(infohash).unwrap(); + + let stored_infohash = driver.get_info_hash_from_whitelist(infohash).unwrap().unwrap(); + + assert_eq!(stored_infohash, infohash); + } + + pub fn it_should_remove_an_infohash_from_the_whitelist(driver: &Arc>) { + let infohash = random_info_hash(); + driver.add_info_hash_to_whitelist(infohash).unwrap(); + + driver.remove_info_hash_from_whitelist(infohash).unwrap(); + + assert!(driver.get_info_hash_from_whitelist(infohash).unwrap().is_none()); + } + + pub fn it_should_fail_trying_to_add_the_same_infohash_twice(driver: &Arc>) { + let infohash = random_info_hash(); + + driver.add_info_hash_to_whitelist(infohash).unwrap(); + let result = driver.add_info_hash_to_whitelist(infohash); + + assert!(result.is_err()); + } + + pub fn it_load_the_whitelist(driver: &Arc>) { + let infohash = random_info_hash(); + driver.add_info_hash_to_whitelist(infohash).unwrap(); + + let whitelist = driver.load_whitelist().unwrap(); + + assert!(whitelist.contains(&infohash)); + } + } } From 700482d14c0a5e417423548a92630f9a58f1b014 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 11 Feb 2025 10:48:29 +0000 Subject: [PATCH 08/10] test: [#1251] reset database before running db driver tests --- .../tracker-core/src/databases/driver/mod.rs | 21 ++++++++++++++----- .../src/databases/driver/mysql.rs | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/tracker-core/src/databases/driver/mod.rs b/packages/tracker-core/src/databases/driver/mod.rs index f4e165f00..07d338915 100644 --- a/packages/tracker-core/src/databases/driver/mod.rs +++ b/packages/tracker-core/src/databases/driver/mod.rs @@ -96,10 +96,7 @@ mod tests { // tests, we share the same database. If we want to isolate the tests in // the future, we can create a new database for each test. - create_database_tables(driver).await.unwrap(); - - // todo: truncate tables otherwise they will increase in size over time. - // That's not a problem on CI when the database is always newly created. + database_setup(driver).await; // Persistent torrents (stats) @@ -121,8 +118,22 @@ mod tests { handling_the_whitelist::it_should_remove_an_infohash_from_the_whitelist(driver); handling_the_whitelist::it_should_fail_trying_to_add_the_same_infohash_twice(driver); handling_the_whitelist::it_load_the_whitelist(driver); + } - driver.drop_database_tables().unwrap(); + /// It initializes the database schema. + /// + /// Since the drop SQL queries don't check if the tables already exist, + /// we have to create them first, and then drop them. + /// + /// The method to drop tables does not use "DROP TABLE IF EXISTS". We can + /// change this function when we update the `Database::drop_database_tables` + /// method to use "DROP TABLE IF EXISTS". + async fn database_setup(driver: &Arc>) { + create_database_tables(driver).await.expect("database tables creation failed"); + driver.drop_database_tables().expect("old database tables deletion failed"); + create_database_tables(driver) + .await + .expect("database tables creation from empty schema failed"); } async fn create_database_tables(driver: &Arc>) -> Result<(), Box> { diff --git a/packages/tracker-core/src/databases/driver/mysql.rs b/packages/tracker-core/src/databases/driver/mysql.rs index a739832d6..1e1e29f36 100644 --- a/packages/tracker-core/src/databases/driver/mysql.rs +++ b/packages/tracker-core/src/databases/driver/mysql.rs @@ -264,7 +264,7 @@ mod tests { `TORRUST_TRACKER_CORE_RUN_MYSQL_DRIVER_TEST=true cargo test` - The `Database`` trait is very simple and we only have one driver that needs + The `Database` trait is very simple and we only have one driver that needs a container. In the future we might want to use different approaches like: - https://github.com/testcontainers/testcontainers-rs/issues/707 From adb96141590c7ae1bbd1bf76b1de0d54d2ca8d07 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 11 Feb 2025 11:05:52 +0000 Subject: [PATCH 09/10] tests: [#1251] add test for loading keys in DB drivers --- .../src/authentication/key/mod.rs | 6 +++ .../tracker-core/src/databases/driver/mod.rs | 41 +++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/tracker-core/src/authentication/key/mod.rs b/packages/tracker-core/src/authentication/key/mod.rs index 33b3b6099..8ec368ebc 100644 --- a/packages/tracker-core/src/authentication/key/mod.rs +++ b/packages/tracker-core/src/authentication/key/mod.rs @@ -67,6 +67,12 @@ pub fn generate_permanent_key() -> PeerKey { generate_key(None) } +/// It generates a new expiring random key [`PeerKey`]. +#[must_use] +pub fn generate_expiring_key(lifetime: Duration) -> PeerKey { + generate_key(Some(lifetime)) +} + /// It generates a new random 32-char authentication [`PeerKey`]. /// /// It can be an expiring or permanent key. diff --git a/packages/tracker-core/src/databases/driver/mod.rs b/packages/tracker-core/src/databases/driver/mod.rs index 07d338915..1e42e4414 100644 --- a/packages/tracker-core/src/databases/driver/mod.rs +++ b/packages/tracker-core/src/databases/driver/mod.rs @@ -104,6 +104,8 @@ mod tests { // Authentication keys (for private trackers) + handling_authentication_keys::it_should_load_the_keys(driver); + // Permanent keys handling_authentication_keys::it_should_save_and_load_permanent_authentication_keys(driver); handling_authentication_keys::it_should_remove_a_permanent_authentication_key(driver); @@ -114,10 +116,10 @@ mod tests { // Whitelist (for listed trackers) + handling_the_whitelist::it_should_load_the_whitelist(driver); handling_the_whitelist::it_should_add_and_get_infohashes(driver); handling_the_whitelist::it_should_remove_an_infohash_from_the_whitelist(driver); handling_the_whitelist::it_should_fail_trying_to_add_the_same_infohash_twice(driver); - handling_the_whitelist::it_load_the_whitelist(driver); } /// It initializes the database schema. @@ -172,9 +174,22 @@ mod tests { use std::sync::Arc; use std::time::Duration; - use crate::authentication::key::{generate_key, generate_permanent_key}; + use crate::authentication::key::{generate_expiring_key, generate_permanent_key}; use crate::databases::Database; + pub fn it_should_load_the_keys(driver: &Arc>) { + let permanent_peer_key = generate_permanent_key(); + driver.add_key_to_keys(&permanent_peer_key).unwrap(); + + let expiring_peer_key = generate_expiring_key(Duration::from_secs(120)); + driver.add_key_to_keys(&expiring_peer_key).unwrap(); + + let keys = driver.load_keys().unwrap(); + + assert!(keys.contains(&permanent_peer_key)); + assert!(keys.contains(&expiring_peer_key)); + } + pub fn it_should_save_and_load_permanent_authentication_keys(driver: &Arc>) { let peer_key = generate_permanent_key(); driver.add_key_to_keys(&peer_key).unwrap(); @@ -185,7 +200,7 @@ mod tests { } pub fn it_should_save_and_load_expiring_authentication_keys(driver: &Arc>) { - let peer_key = generate_key(Some(Duration::from_secs(120))); + let peer_key = generate_expiring_key(Duration::from_secs(120)); driver.add_key_to_keys(&peer_key).unwrap(); let stored_peer_key = driver.get_key_from_keys(&peer_key.key()).unwrap().unwrap(); @@ -204,7 +219,7 @@ mod tests { } pub fn it_should_remove_an_expiring_authentication_key(driver: &Arc>) { - let peer_key = generate_key(Some(Duration::from_secs(120))); + let peer_key = generate_expiring_key(Duration::from_secs(120)); driver.add_key_to_keys(&peer_key).unwrap(); driver.remove_key_from_keys(&peer_key.key()).unwrap(); @@ -220,6 +235,15 @@ mod tests { use crate::core_tests::random_info_hash; use crate::databases::Database; + pub fn it_should_load_the_whitelist(driver: &Arc>) { + let infohash = random_info_hash(); + driver.add_info_hash_to_whitelist(infohash).unwrap(); + + let whitelist = driver.load_whitelist().unwrap(); + + assert!(whitelist.contains(&infohash)); + } + pub fn it_should_add_and_get_infohashes(driver: &Arc>) { let infohash = random_info_hash(); @@ -247,14 +271,5 @@ mod tests { assert!(result.is_err()); } - - pub fn it_load_the_whitelist(driver: &Arc>) { - let infohash = random_info_hash(); - driver.add_info_hash_to_whitelist(infohash).unwrap(); - - let whitelist = driver.load_whitelist().unwrap(); - - assert!(whitelist.contains(&infohash)); - } } } From 2cd1c65cbff75895ec26dfc6943319cf2c665399 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 11 Feb 2025 11:34:43 +0000 Subject: [PATCH 10/10] test: [#1251] add tests for database driver error converters --- packages/tracker-core/src/databases/error.rs | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/tracker-core/src/databases/error.rs b/packages/tracker-core/src/databases/error.rs index 4d64baf48..0f3207587 100644 --- a/packages/tracker-core/src/databases/error.rs +++ b/packages/tracker-core/src/databases/error.rs @@ -102,3 +102,39 @@ impl From<(r2d2::Error, Driver)> for Error { } } } + +#[cfg(test)] +mod tests { + use r2d2_mysql::mysql; + + use crate::databases::error::Error; + + #[test] + fn it_should_build_a_database_error_from_a_rusqlite_error() { + let err: Error = r2d2_sqlite::rusqlite::Error::InvalidQuery.into(); + + assert!(matches!(err, Error::InvalidQuery { .. })); + } + + #[test] + fn it_should_build_an_specific_database_error_from_a_no_rows_returned_rusqlite_error() { + let err: Error = r2d2_sqlite::rusqlite::Error::QueryReturnedNoRows.into(); + + assert!(matches!(err, Error::QueryReturnedNoRows { .. })); + } + + #[test] + fn it_should_build_a_database_error_from_a_mysql_error() { + let url_err = mysql::error::UrlError::BadUrl; + let err: Error = r2d2_mysql::mysql::Error::UrlError(url_err).into(); + + assert!(matches!(err, Error::InvalidQuery { .. })); + } + + #[test] + fn it_should_build_a_database_error_from_a_mysql_url_error() { + let err: Error = mysql::error::UrlError::BadUrl.into(); + + assert!(matches!(err, Error::ConnectionError { .. })); + } +}