Skip to content

Commit

Permalink
test: [#108] add e2e test for auth key generation API endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Nov 23, 2022
1 parent 1df109d commit 746302d
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
/config.toml
/data.db
/.vscode/launch.json
/tests/data.db
2 changes: 1 addition & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pub mod server;
pub mod resources;
pub mod server;
56 changes: 44 additions & 12 deletions src/api/resources/auth_key_resource.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
use std::convert::From;

use serde::{Deserialize, Serialize};

use crate::key::AuthKey;
use crate::protocol::clock::DurationSinceUnixEpoch;

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

impl AuthKeyResource {
pub fn from_auth_key(auth_key: &AuthKey) -> Self {
Self {
key: auth_key.key.clone(),
valid_until: auth_key.valid_until.map(|duration| duration.as_secs()),
impl From<AuthKeyResource> for AuthKey {
fn from(auth_key_resource: AuthKeyResource) -> Self {
AuthKey {
key: auth_key_resource.key,
valid_until: auth_key_resource
.valid_until
.map(|valid_until| DurationSinceUnixEpoch::new(valid_until, 0)),
}
}
}

impl From<AuthKey> for AuthKeyResource {
fn from(auth_key: AuthKey) -> Self {
AuthKeyResource {
key: auth_key.key,
valid_until: auth_key.valid_until.map(|valid_until| valid_until.as_secs()),
}
}
}
Expand All @@ -26,25 +40,43 @@ mod tests {
use crate::protocol::clock::{DefaultClock, TimeNow};

#[test]
fn it_should_be_instantiated_from_an_auth_key() {
let expire_time = DefaultClock::add(&Duration::new(60, 0)).unwrap();
fn it_should_be_convertible_into_an_auth_key() {
let duration_in_secs = 60;

let auth_key_resource = AuthKeyResource {
key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line
valid_until: Some(duration_in_secs),
};

assert_eq!(
AuthKey::from(auth_key_resource),
AuthKey {
key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line
valid_until: Some(DefaultClock::add(&Duration::new(duration_in_secs, 0)).unwrap())
}
)
}

#[test]
fn it_should_be_convertible_from_an_auth_key() {
let duration_in_secs = 60;

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

assert_eq!(
AuthKeyResource::from_auth_key(&auth_key_resource),
AuthKeyResource::from(auth_key),
AuthKeyResource {
key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line
valid_until: Some(expire_time.as_secs())
valid_until: Some(duration_in_secs)
}
)
}

#[test]
fn it_should_be_converted_to_json() {
fn it_should_be_convertible_into_json() {
assert_eq!(
serde_json::to_string(&AuthKeyResource {
key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line
Expand Down
4 changes: 2 additions & 2 deletions src/api/resources/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! These are the Rest API resources.
//!
//!
//! WIP. Not all endpoints have their resource structs.
//!
//!
//! - [x] AuthKeys
//! - [ ] ...
//! - [ ] ...
Expand Down
5 changes: 2 additions & 3 deletions src/api/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ use std::time::Duration;
use serde::{Deserialize, Serialize};
use warp::{filters, reply, serve, Filter};

use super::resources::auth_key_resource::AuthKeyResource;
use crate::peer::TorrentPeer;
use crate::protocol::common::*;
use crate::tracker::tracker::TorrentTracker;

use super::resources::auth_key_resource::AuthKeyResource;

#[derive(Deserialize, Debug)]
struct TorrentInfoQuery {
offset: Option<u32>,
Expand Down Expand Up @@ -272,7 +271,7 @@ pub fn start(socket_addr: SocketAddr, tracker: Arc<TorrentTracker>) -> impl warp
})
.and_then(|(seconds_valid, tracker): (u64, Arc<TorrentTracker>)| async move {
match tracker.generate_auth_key(Duration::from_secs(seconds_valid)).await {
Ok(auth_key) => Ok(warp::reply::json(&AuthKeyResource::from_auth_key(&auth_key))),
Ok(auth_key) => Ok(warp::reply::json(&AuthKeyResource::from(auth_key))),
Err(..) => Err(warp::reject::custom(ActionStatus::Err {
reason: "failed to generate key".into(),
})),
Expand Down
112 changes: 112 additions & 0 deletions tests/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/// Integration tests for the tracker API
///
/// cargo test tracker_api -- --nocapture
extern crate rand;

mod common;

mod tracker_api {
use core::panic;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use tokio::task::JoinHandle;
use tokio::time::{sleep, Duration};
use torrust_tracker::api::resources::auth_key_resource::AuthKeyResource;
use torrust_tracker::jobs::tracker_api;
use torrust_tracker::tracker::key::AuthKey;
use torrust_tracker::tracker::statistics::StatsTracker;
use torrust_tracker::tracker::tracker::TorrentTracker;
use torrust_tracker::{ephemeral_instance_keys, logging, static_time, Configuration};

use crate::common::ephemeral_random_port;

#[tokio::test]
async fn should_generate_a_new_auth_key() {
let configuration = tracker_configuration();
let api_server = new_running_api_server(configuration.clone()).await;

let bind_address = api_server.bind_address.unwrap().clone();
let seconds_valid = 60;
let api_token = configuration.http_api.access_tokens.get_key_value("admin").unwrap().1.clone();

let url = format!("http://{}/api/key/{}?token={}", &bind_address, &seconds_valid, &api_token);

let auth_key: AuthKeyResource = reqwest::Client::new().post(url).send().await.unwrap().json().await.unwrap();

// Verify the key with the tracker
assert!(api_server
.tracker
.unwrap()
.verify_auth_key(&AuthKey::from(auth_key))
.await
.is_ok());
}

fn tracker_configuration() -> Arc<Configuration> {
let mut config = Configuration::default();
config.log_level = Some("off".to_owned());
config.http_api.bind_address = format!("127.0.0.1:{}", ephemeral_random_port());
config.db_path = "./tests/data.db".to_owned();
Arc::new(config)
}

async fn new_running_api_server(configuration: Arc<Configuration>) -> ApiServer {
let mut api_server = ApiServer::new();
api_server.start(configuration).await;
api_server
}

pub struct ApiServer {
pub started: AtomicBool,
pub job: Option<JoinHandle<()>>,
pub bind_address: Option<String>,
pub tracker: Option<Arc<TorrentTracker>>,
}

impl ApiServer {
pub fn new() -> Self {
Self {
started: AtomicBool::new(false),
job: None,
bind_address: None,
tracker: None,
}
}

pub async fn start(&mut self, configuration: Arc<Configuration>) {
if !self.started.load(Ordering::Relaxed) {
self.bind_address = Some(configuration.http_api.bind_address.clone());

// Set the time of Torrust app starting
lazy_static::initialize(&static_time::TIME_AT_APP_START);

// Initialize the Ephemeral Instance Random Seed
lazy_static::initialize(&ephemeral_instance_keys::RANDOM_SEED);

// Initialize stats tracker
let (stats_event_sender, stats_repository) = StatsTracker::new_active_instance();

// Initialize Torrust tracker
let tracker = match TorrentTracker::new(configuration.clone(), Some(stats_event_sender), stats_repository) {
Ok(tracker) => Arc::new(tracker),
Err(error) => {
panic!("{}", error)
}
};
self.tracker = Some(tracker.clone());

// Initialize logging
logging::setup_logging(&configuration);

// Start the HTTP API job
self.job = Some(tracker_api::start_job(&configuration, tracker.clone()));

self.started.store(true, Ordering::Relaxed);

// Wait to give time to the API server to be ready to accept requests
sleep(Duration::from_millis(100)).await;
}
}
}
}
8 changes: 8 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use rand::{thread_rng, Rng};

pub fn ephemeral_random_port() -> u16 {
// todo: this may produce random test failures because two tests can try to bind the same port.
// We could create a pool of available ports (with read/write lock)
let mut rng = thread_rng();
rng.gen_range(49152..65535)
}
19 changes: 8 additions & 11 deletions tests/udp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
/// cargo test udp_tracker_server -- --nocapture
extern crate rand;

mod common;

mod udp_tracker_server {
use core::panic;
use std::io::Cursor;
Expand All @@ -14,14 +16,15 @@ mod udp_tracker_server {
AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, InfoHash, NumberOfBytes, NumberOfPeers, PeerId, PeerKey,
Port, Request, Response, ScrapeRequest, TransactionId,
};
use rand::{thread_rng, Rng};
use tokio::net::UdpSocket;
use tokio::task::JoinHandle;
use torrust_tracker::jobs::udp_tracker;
use torrust_tracker::tracker::statistics::StatsTracker;
use torrust_tracker::tracker::tracker::TorrentTracker;
use torrust_tracker::udp::MAX_PACKET_SIZE;
use torrust_tracker::{logging, static_time, Configuration};
use torrust_tracker::{ephemeral_instance_keys, logging, static_time, Configuration};

use crate::common::ephemeral_random_port;

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

// Initialize the Ephemeral Instance Random Seed
lazy_static::initialize(&ephemeral_instance_keys::RANDOM_SEED);

// Initialize stats tracker
let (stats_event_sender, stats_repository) = StatsTracker::new_active_instance();

Expand Down Expand Up @@ -162,15 +168,6 @@ mod udp_tracker_server {
[0; MAX_PACKET_SIZE]
}

/// Generates a random ephemeral port for a client source address
fn ephemeral_random_port() -> u16 {
// todo: this may produce random test failures because two tests can try to bind the same port.
// We could either use the same client for all tests (slower) or
// create a pool of available ports (with read/write lock)
let mut rng = thread_rng();
rng.gen_range(49152..65535)
}

/// Generates the source address for the UDP client
fn source_address(port: u16) -> String {
format!("127.0.0.1:{}", port)
Expand Down

0 comments on commit 746302d

Please sign in to comment.