Skip to content

Commit 92c50e6

Browse files
committed
test: [#108] add e2e test for auth key generation API endpoint
1 parent 1df109d commit 92c50e6

File tree

8 files changed

+185
-29
lines changed

8 files changed

+185
-29
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
/config.toml
77
/data.db
88
/.vscode/launch.json
9+

src/api/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
pub mod server;
21
pub mod resources;
2+
pub mod server;

src/api/resources/auth_key_resource.rs

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
1+
use std::convert::From;
2+
13
use serde::{Deserialize, Serialize};
24

35
use crate::key::AuthKey;
6+
use crate::protocol::clock::DurationSinceUnixEpoch;
47

58
#[derive(Serialize, Deserialize, Debug, PartialEq)]
69
pub struct AuthKeyResource {
710
pub key: String,
811
pub valid_until: Option<u64>,
912
}
1013

11-
impl AuthKeyResource {
12-
pub fn from_auth_key(auth_key: &AuthKey) -> Self {
13-
Self {
14-
key: auth_key.key.clone(),
15-
valid_until: auth_key.valid_until.map(|duration| duration.as_secs()),
14+
impl From<AuthKeyResource> for AuthKey {
15+
fn from(auth_key_resource: AuthKeyResource) -> Self {
16+
AuthKey {
17+
key: auth_key_resource.key,
18+
valid_until: auth_key_resource
19+
.valid_until
20+
.map(|valid_until| DurationSinceUnixEpoch::new(valid_until, 0)),
21+
}
22+
}
23+
}
24+
25+
impl From<AuthKey> for AuthKeyResource {
26+
fn from(auth_key: AuthKey) -> Self {
27+
AuthKeyResource {
28+
key: auth_key.key,
29+
valid_until: auth_key.valid_until.map(|valid_until| valid_until.as_secs()),
1630
}
1731
}
1832
}
@@ -26,25 +40,43 @@ mod tests {
2640
use crate::protocol::clock::{DefaultClock, TimeNow};
2741

2842
#[test]
29-
fn it_should_be_instantiated_from_an_auth_key() {
30-
let expire_time = DefaultClock::add(&Duration::new(60, 0)).unwrap();
43+
fn it_should_be_convertible_into_an_auth_key() {
44+
let duration_in_secs = 60;
45+
46+
let auth_key_resource = AuthKeyResource {
47+
key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line
48+
valid_until: Some(duration_in_secs),
49+
};
50+
51+
assert_eq!(
52+
AuthKey::from(auth_key_resource),
53+
AuthKey {
54+
key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line
55+
valid_until: Some(DefaultClock::add(&Duration::new(duration_in_secs, 0)).unwrap())
56+
}
57+
)
58+
}
59+
60+
#[test]
61+
fn it_should_be_convertible_from_an_auth_key() {
62+
let duration_in_secs = 60;
3163

32-
let auth_key_resource = AuthKey {
64+
let auth_key = AuthKey {
3365
key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line
34-
valid_until: Some(expire_time),
66+
valid_until: Some(DefaultClock::add(&Duration::new(duration_in_secs, 0)).unwrap()),
3567
};
3668

3769
assert_eq!(
38-
AuthKeyResource::from_auth_key(&auth_key_resource),
70+
AuthKeyResource::from(auth_key),
3971
AuthKeyResource {
4072
key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line
41-
valid_until: Some(expire_time.as_secs())
73+
valid_until: Some(duration_in_secs)
4274
}
4375
)
4476
}
4577

4678
#[test]
47-
fn it_should_be_converted_to_json() {
79+
fn it_should_be_convertible_into_json() {
4880
assert_eq!(
4981
serde_json::to_string(&AuthKeyResource {
5082
key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line

src/api/resources/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! These are the Rest API resources.
2-
//!
2+
//!
33
//! WIP. Not all endpoints have their resource structs.
4-
//!
4+
//!
55
//! - [x] AuthKeys
66
//! - [ ] ...
77
//! - [ ] ...

src/api/server.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ use std::time::Duration;
77
use serde::{Deserialize, Serialize};
88
use warp::{filters, reply, serve, Filter};
99

10+
use super::resources::auth_key_resource::AuthKeyResource;
1011
use crate::peer::TorrentPeer;
1112
use crate::protocol::common::*;
1213
use crate::tracker::tracker::TorrentTracker;
1314

14-
use super::resources::auth_key_resource::AuthKeyResource;
15-
1615
#[derive(Deserialize, Debug)]
1716
struct TorrentInfoQuery {
1817
offset: Option<u32>,
@@ -272,7 +271,7 @@ pub fn start(socket_addr: SocketAddr, tracker: Arc<TorrentTracker>) -> impl warp
272271
})
273272
.and_then(|(seconds_valid, tracker): (u64, Arc<TorrentTracker>)| async move {
274273
match tracker.generate_auth_key(Duration::from_secs(seconds_valid)).await {
275-
Ok(auth_key) => Ok(warp::reply::json(&AuthKeyResource::from_auth_key(&auth_key))),
274+
Ok(auth_key) => Ok(warp::reply::json(&AuthKeyResource::from(auth_key))),
276275
Err(..) => Err(warp::reject::custom(ActionStatus::Err {
277276
reason: "failed to generate key".into(),
278277
})),

tests/api.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/// Integration tests for the tracker API
2+
///
3+
/// cargo test tracker_api -- --nocapture
4+
extern crate rand;
5+
6+
mod common;
7+
8+
mod tracker_api {
9+
use core::panic;
10+
use std::env;
11+
use std::sync::atomic::{AtomicBool, Ordering};
12+
use std::sync::Arc;
13+
14+
use tokio::task::JoinHandle;
15+
use tokio::time::{sleep, Duration};
16+
use torrust_tracker::api::resources::auth_key_resource::AuthKeyResource;
17+
use torrust_tracker::jobs::tracker_api;
18+
use torrust_tracker::tracker::key::AuthKey;
19+
use torrust_tracker::tracker::statistics::StatsTracker;
20+
use torrust_tracker::tracker::tracker::TorrentTracker;
21+
use torrust_tracker::{ephemeral_instance_keys, logging, static_time, Configuration};
22+
23+
use crate::common::ephemeral_random_port;
24+
25+
#[tokio::test]
26+
async fn should_generate_a_new_auth_key() {
27+
let configuration = tracker_configuration();
28+
let api_server = new_running_api_server(configuration.clone()).await;
29+
30+
let bind_address = api_server.bind_address.unwrap().clone();
31+
let seconds_valid = 60;
32+
let api_token = configuration.http_api.access_tokens.get_key_value("admin").unwrap().1.clone();
33+
34+
let url = format!("http://{}/api/key/{}?token={}", &bind_address, &seconds_valid, &api_token);
35+
36+
let auth_key: AuthKeyResource = reqwest::Client::new().post(url).send().await.unwrap().json().await.unwrap();
37+
38+
// Verify the key with the tracker
39+
assert!(api_server
40+
.tracker
41+
.unwrap()
42+
.verify_auth_key(&AuthKey::from(auth_key))
43+
.await
44+
.is_ok());
45+
}
46+
47+
fn tracker_configuration() -> Arc<Configuration> {
48+
let mut config = Configuration::default();
49+
config.log_level = Some("off".to_owned());
50+
51+
config.http_api.bind_address = format!("127.0.0.1:{}", ephemeral_random_port());
52+
53+
// Temp database
54+
let temp_directory = env::temp_dir();
55+
let temp_file = temp_directory.join("data.db");
56+
config.db_path = temp_file.to_str().unwrap().to_owned();
57+
58+
Arc::new(config)
59+
}
60+
61+
async fn new_running_api_server(configuration: Arc<Configuration>) -> ApiServer {
62+
let mut api_server = ApiServer::new();
63+
api_server.start(configuration).await;
64+
api_server
65+
}
66+
67+
pub struct ApiServer {
68+
pub started: AtomicBool,
69+
pub job: Option<JoinHandle<()>>,
70+
pub bind_address: Option<String>,
71+
pub tracker: Option<Arc<TorrentTracker>>,
72+
}
73+
74+
impl ApiServer {
75+
pub fn new() -> Self {
76+
Self {
77+
started: AtomicBool::new(false),
78+
job: None,
79+
bind_address: None,
80+
tracker: None,
81+
}
82+
}
83+
84+
pub async fn start(&mut self, configuration: Arc<Configuration>) {
85+
if !self.started.load(Ordering::Relaxed) {
86+
self.bind_address = Some(configuration.http_api.bind_address.clone());
87+
88+
// Set the time of Torrust app starting
89+
lazy_static::initialize(&static_time::TIME_AT_APP_START);
90+
91+
// Initialize the Ephemeral Instance Random Seed
92+
lazy_static::initialize(&ephemeral_instance_keys::RANDOM_SEED);
93+
94+
// Initialize stats tracker
95+
let (stats_event_sender, stats_repository) = StatsTracker::new_active_instance();
96+
97+
// Initialize Torrust tracker
98+
let tracker = match TorrentTracker::new(configuration.clone(), Some(stats_event_sender), stats_repository) {
99+
Ok(tracker) => Arc::new(tracker),
100+
Err(error) => {
101+
panic!("{}", error)
102+
}
103+
};
104+
self.tracker = Some(tracker.clone());
105+
106+
// Initialize logging
107+
logging::setup_logging(&configuration);
108+
109+
// Start the HTTP API job
110+
self.job = Some(tracker_api::start_job(&configuration, tracker.clone()));
111+
112+
self.started.store(true, Ordering::Relaxed);
113+
114+
// Wait to give time to the API server to be ready to accept requests
115+
sleep(Duration::from_millis(100)).await;
116+
}
117+
}
118+
}
119+
}

tests/common/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use rand::{thread_rng, Rng};
2+
3+
pub fn ephemeral_random_port() -> u16 {
4+
// todo: this may produce random test failures because two tests can try to bind the same port.
5+
// We could create a pool of available ports (with read/write lock)
6+
let mut rng = thread_rng();
7+
rng.gen_range(49152..65535)
8+
}

tests/udp.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
/// cargo test udp_tracker_server -- --nocapture
44
extern crate rand;
55

6+
mod common;
7+
68
mod udp_tracker_server {
79
use core::panic;
810
use std::io::Cursor;
@@ -14,14 +16,15 @@ mod udp_tracker_server {
1416
AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, InfoHash, NumberOfBytes, NumberOfPeers, PeerId, PeerKey,
1517
Port, Request, Response, ScrapeRequest, TransactionId,
1618
};
17-
use rand::{thread_rng, Rng};
1819
use tokio::net::UdpSocket;
1920
use tokio::task::JoinHandle;
2021
use torrust_tracker::jobs::udp_tracker;
2122
use torrust_tracker::tracker::statistics::StatsTracker;
2223
use torrust_tracker::tracker::tracker::TorrentTracker;
2324
use torrust_tracker::udp::MAX_PACKET_SIZE;
24-
use torrust_tracker::{logging, static_time, Configuration};
25+
use torrust_tracker::{ephemeral_instance_keys, logging, static_time, Configuration};
26+
27+
use crate::common::ephemeral_random_port;
2528

2629
fn tracker_configuration() -> Arc<Configuration> {
2730
let mut config = Configuration::default();
@@ -50,6 +53,9 @@ mod udp_tracker_server {
5053
// Set the time of Torrust app starting
5154
lazy_static::initialize(&static_time::TIME_AT_APP_START);
5255

56+
// Initialize the Ephemeral Instance Random Seed
57+
lazy_static::initialize(&ephemeral_instance_keys::RANDOM_SEED);
58+
5359
// Initialize stats tracker
5460
let (stats_event_sender, stats_repository) = StatsTracker::new_active_instance();
5561

@@ -162,15 +168,6 @@ mod udp_tracker_server {
162168
[0; MAX_PACKET_SIZE]
163169
}
164170

165-
/// Generates a random ephemeral port for a client source address
166-
fn ephemeral_random_port() -> u16 {
167-
// todo: this may produce random test failures because two tests can try to bind the same port.
168-
// We could either use the same client for all tests (slower) or
169-
// create a pool of available ports (with read/write lock)
170-
let mut rng = thread_rng();
171-
rng.gen_range(49152..65535)
172-
}
173-
174171
/// Generates the source address for the UDP client
175172
fn source_address(port: u16) -> String {
176173
format!("127.0.0.1:{}", port)

0 commit comments

Comments
 (0)