Skip to content

Commit 460489c

Browse files
committed
test(api): [#142] improved api test coverage
1 parent 07364f4 commit 460489c

File tree

3 files changed

+511
-56
lines changed

3 files changed

+511
-56
lines changed

src/tracker/auth.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use derive_more::{Display, Error};
44
use log::debug;
55
use rand::distributions::Alphanumeric;
66
use rand::{thread_rng, Rng};
7-
use serde::Serialize;
7+
use serde::{Deserialize, Serialize};
88

99
use crate::protocol::clock::{Current, DurationSinceUnixEpoch, Time, TimeNow};
1010
use crate::protocol::common::AUTH_KEY_LENGTH;
@@ -48,7 +48,7 @@ pub fn verify(auth_key: &Key) -> Result<(), Error> {
4848
}
4949
}
5050

51-
#[derive(Serialize, Debug, Eq, PartialEq, Clone)]
51+
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
5252
pub struct Key {
5353
pub key: String,
5454
pub valid_until: Option<DurationSinceUnixEpoch>,

tests/api/mod.rs

Lines changed: 151 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
use core::panic;
22
use std::env;
3+
use std::fmt::{Display, Formatter, Result};
34
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
45
use std::sync::Arc;
56

67
use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes};
78
use reqwest::Response;
8-
use torrust_tracker::api::resource::auth_key::AuthKey;
9-
use torrust_tracker::api::resource::stats::Stats;
10-
use torrust_tracker::api::resource::torrent::{self, Torrent};
119
use torrust_tracker::config::Configuration;
1210
use torrust_tracker::jobs::tracker_api;
1311
use torrust_tracker::protocol::clock::DurationSinceUnixEpoch;
@@ -51,14 +49,21 @@ pub fn tracker_configuration() -> Arc<Configuration> {
5149
#[derive(Clone)]
5250
pub struct ConnectionInfo {
5351
pub bind_address: String,
54-
pub api_token: String,
52+
pub api_token: Option<String>,
5553
}
5654

5755
impl ConnectionInfo {
58-
pub fn new(bind_address: &str, api_token: &str) -> Self {
56+
pub fn authenticated(bind_address: &str, api_token: &str) -> Self {
5957
Self {
6058
bind_address: bind_address.to_string(),
61-
api_token: api_token.to_string(),
59+
api_token: Some(api_token.to_string()),
60+
}
61+
}
62+
63+
pub fn anonymous(bind_address: &str) -> Self {
64+
Self {
65+
bind_address: bind_address.to_string(),
66+
api_token: None,
6267
}
6368
}
6469
}
@@ -73,7 +78,7 @@ pub async fn start_custom_api_server(configuration: Arc<Configuration>) -> Serve
7378
}
7479

7580
async fn start(configuration: Arc<Configuration>) -> Server {
76-
let connection_info = ConnectionInfo::new(
81+
let connection_info = ConnectionInfo::authenticated(
7782
&configuration.http_api.bind_address.clone(),
7883
&configuration.http_api.access_tokens.get_key_value("admin").unwrap().1.clone(),
7984
);
@@ -117,6 +122,10 @@ impl Server {
117122
self.connection_info.clone()
118123
}
119124

125+
pub fn get_bind_address(&self) -> String {
126+
self.connection_info.bind_address.clone()
127+
}
128+
120129
/// Add a torrent to the tracker
121130
pub async fn add_torrent(&self, info_hash: &InfoHash, peer: &Peer) {
122131
self.tracker.update_torrent_with_peer_and_get_stats(info_hash, peer).await;
@@ -127,53 +136,168 @@ pub struct Client {
127136
connection_info: ConnectionInfo,
128137
}
129138

139+
type ReqwestQuery = Vec<ReqwestQueryParam>;
140+
type ReqwestQueryParam = (String, String);
141+
142+
#[derive(Default, Debug)]
143+
pub struct Query {
144+
params: Vec<QueryParam>,
145+
}
146+
147+
impl Query {
148+
pub fn empty() -> Self {
149+
Self { params: vec![] }
150+
}
151+
152+
pub fn params(params: Vec<QueryParam>) -> Self {
153+
Self { params }
154+
}
155+
156+
pub fn add_param(&mut self, param: QueryParam) {
157+
self.params.push(param);
158+
}
159+
160+
fn with_token(token: &str) -> Self {
161+
Self {
162+
params: vec![QueryParam::new("token", token)],
163+
}
164+
}
165+
}
166+
167+
impl From<Query> for ReqwestQuery {
168+
fn from(url_search_params: Query) -> Self {
169+
url_search_params
170+
.params
171+
.iter()
172+
.map(|param| ReqwestQueryParam::from((*param).clone()))
173+
.collect()
174+
}
175+
}
176+
177+
#[derive(Clone, Debug)]
178+
pub struct QueryParam {
179+
name: UrlEncoded,
180+
value: UrlEncoded,
181+
}
182+
183+
impl QueryParam {
184+
pub fn new(name: &str, value: &str) -> Self {
185+
Self {
186+
name: UrlEncoded::new(name),
187+
value: UrlEncoded::new(value),
188+
}
189+
}
190+
}
191+
192+
impl From<QueryParam> for ReqwestQueryParam {
193+
fn from(param: QueryParam) -> Self {
194+
(param.name.to_string(), param.value.to_string())
195+
}
196+
}
197+
198+
#[derive(Clone, Debug)]
199+
pub struct UrlEncoded {
200+
value: String,
201+
}
202+
203+
impl UrlEncoded {
204+
fn new(value: &str) -> Self {
205+
Self {
206+
value: value.to_string(), // todo encode
207+
}
208+
}
209+
}
210+
211+
impl Display for UrlEncoded {
212+
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
213+
write!(f, "{}", self.value)
214+
}
215+
}
216+
130217
impl Client {
131218
pub fn new(connection_info: ConnectionInfo) -> Self {
132219
Self { connection_info }
133220
}
134221

135-
pub async fn generate_auth_key(&self, seconds_valid: i32) -> AuthKey {
136-
self.post(&format!("key/{}", &seconds_valid)).await.json().await.unwrap()
222+
pub async fn generate_auth_key(&self, seconds_valid: i32) -> Response {
223+
self.post(&format!("key/{}", &seconds_valid)).await
224+
}
225+
226+
pub async fn delete_auth_key(&self, key: &str) -> Response {
227+
self.delete(&format!("key/{}", &key)).await
228+
}
229+
230+
pub async fn reload_keys(&self) -> Response {
231+
self.get("keys/reload", Query::default()).await
137232
}
138233

139234
pub async fn whitelist_a_torrent(&self, info_hash: &str) -> Response {
140235
self.post(&format!("whitelist/{}", &info_hash)).await
141236
}
142237

143-
pub async fn get_torrent(&self, info_hash: &str) -> Torrent {
144-
self.get(&format!("torrent/{}", &info_hash))
145-
.await
146-
.json::<Torrent>()
147-
.await
148-
.unwrap()
238+
pub async fn remove_torrent_from_whitelist(&self, info_hash: &str) -> Response {
239+
self.delete(&format!("whitelist/{}", &info_hash)).await
149240
}
150241

151-
pub async fn get_torrents(&self) -> Vec<torrent::ListItem> {
152-
self.get("torrents").await.json::<Vec<torrent::ListItem>>().await.unwrap()
242+
pub async fn reload_whitelist(&self) -> Response {
243+
self.get("whitelist/reload", Query::default()).await
153244
}
154245

155-
pub async fn get_tracker_statistics(&self) -> Stats {
156-
self.get("stats").await.json::<Stats>().await.unwrap()
246+
pub async fn get_torrent(&self, info_hash: &str) -> Response {
247+
self.get(&format!("torrent/{}", &info_hash), Query::default()).await
157248
}
158249

159-
async fn get(&self, path: &str) -> Response {
250+
pub async fn get_torrents(&self, params: Query) -> Response {
251+
self.get("torrents", params).await
252+
}
253+
254+
pub async fn get_tracker_statistics(&self) -> Response {
255+
self.get("stats", Query::default()).await
256+
}
257+
258+
async fn get(&self, path: &str, params: Query) -> Response {
259+
let mut query: Query = params;
260+
261+
if let Some(token) = &self.connection_info.api_token {
262+
query.add_param(QueryParam::new("token", token));
263+
};
264+
160265
reqwest::Client::builder()
161266
.build()
162267
.unwrap()
163-
.get(self.url(path))
268+
.get(self.base_url(path))
269+
.query(&ReqwestQuery::from(query))
164270
.send()
165271
.await
166272
.unwrap()
167273
}
168274

169275
async fn post(&self, path: &str) -> Response {
170-
reqwest::Client::new().post(self.url(path).clone()).send().await.unwrap()
276+
reqwest::Client::new()
277+
.post(self.base_url(path).clone())
278+
.query(&ReqwestQuery::from(self.query_with_token()))
279+
.send()
280+
.await
281+
.unwrap()
171282
}
172283

173-
fn url(&self, path: &str) -> String {
174-
format!(
175-
"http://{}/api/{path}?token={}",
176-
&self.connection_info.bind_address, &self.connection_info.api_token
177-
)
284+
async fn delete(&self, path: &str) -> Response {
285+
reqwest::Client::new()
286+
.delete(self.base_url(path).clone())
287+
.query(&ReqwestQuery::from(self.query_with_token()))
288+
.send()
289+
.await
290+
.unwrap()
291+
}
292+
293+
fn base_url(&self, path: &str) -> String {
294+
format!("http://{}/api/{path}", &self.connection_info.bind_address)
295+
}
296+
297+
fn query_with_token(&self) -> Query {
298+
match &self.connection_info.api_token {
299+
Some(token) => Query::with_token(token),
300+
None => Query::default(),
301+
}
178302
}
179303
}

0 commit comments

Comments
 (0)