Skip to content

Commit 401aeea

Browse files
authored
Merge pull request #20 from hackpulsar/integration-tests
Integration tests
2 parents 240bb09 + 8c157a3 commit 401aeea

17 files changed

Lines changed: 1857 additions & 90 deletions

File tree

Cargo.lock

Lines changed: 1070 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,12 @@ tokio-util = { version = "0.7", features = ["io"] }
2222
rand = "0.8.0"
2323
log = "0.4"
2424
env_logger = "0.11"
25-
openssl = "0.10"
25+
openssl = "0.10"
26+
27+
[dev-dependencies]
28+
testcontainers = "0.27"
29+
testcontainers-modules = { version = "0.15", features = ["postgres", "redis"]}
30+
tempfile = "3"
31+
serde_json = "1"
32+
actix-http = "3"
33+
actix-multipart-test = { git = "https://github.com/linasdev/actix-multipart-test", branch = "main"}

src/lib.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
pub mod models;
2+
pub mod routes;
3+
pub mod services;
4+
pub mod utils;
5+
6+
use deadpool_redis::{Config, Runtime};
7+
use sqlx::{Postgres};
8+
use log::info;
9+
10+
// Holds app state
11+
pub struct AppState {
12+
pub secret: String,
13+
pub db_pool: sqlx::Pool<Postgres>,
14+
pub redis_pool: deadpool_redis::Pool,
15+
pub storage_dir: String
16+
}
17+
18+
// Connects to a database
19+
pub async fn create_db_pool(db_url: String) -> Result<sqlx::Pool<Postgres>, sqlx::Error> {
20+
info!("Connecting to PostgreSQL...");
21+
let pool = sqlx::postgres::PgPool::connect(db_url.as_str()).await?;
22+
info!("Connected to PostgreSQL.");
23+
Ok(pool)
24+
}
25+
26+
// Creates a new redis pool
27+
pub fn create_redis_pool(redis_url: String) -> Result<deadpool_redis::Pool, deadpool_redis::CreatePoolError> {
28+
info!("Connecting to Redis...");
29+
let pool = Config::from_url(redis_url.as_str()).create_pool(Some(Runtime::Tokio1))?;
30+
info!("Connected to Redis.");
31+
Ok(pool)
32+
}

src/main.rs

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,14 @@
1-
mod models;
2-
mod routes;
3-
mod services;
4-
mod utils;
5-
61
use core::panic;
72

83
use actix_multipart::form::tempfile::TempFileConfig;
94
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
105
use actix_web::{App, HttpServer, web};
11-
use deadpool_redis::{Config, Runtime};
12-
use sqlx::{Postgres};
136
use log::{info, error};
147

8+
use storage_crab::*;
159
use crate::routes::init_routes;
1610
use crate::utils::generate_shared_secret;
1711

18-
// Holds app state
19-
pub struct AppState {
20-
secret: String,
21-
db_pool: sqlx::Pool<Postgres>,
22-
redis_pool: deadpool_redis::Pool,
23-
}
24-
2512
#[actix_web::main]
2613
async fn main() -> std::io::Result<()> {
2714
// Loading environment variables
@@ -72,27 +59,12 @@ async fn main() -> std::io::Result<()> {
7259
secret: secret.clone(),
7360
db_pool: db_pool.clone(),
7461
redis_pool: redis_pool.clone(),
62+
storage_dir: std::env::var("FILES_STORAGE_PATH").unwrap()
7563
}))
76-
.app_data(TempFileConfig::default().directory(std::env::var("FILES_STORAGE_PATH").unwrap()))
64+
.app_data(TempFileConfig::default().directory(std::env::var("TMP_FILES_STORAGE").unwrap()))
7765
.configure(init_routes)
7866
})
7967
.bind_openssl("0.0.0.0:8080", builder)?
8068
.run()
8169
.await
8270
}
83-
84-
// Connects to a database
85-
async fn create_db_pool(db_url: String) -> Result<sqlx::Pool<Postgres>, sqlx::Error> {
86-
info!("Connecting to PostgreSQL...");
87-
let pool = sqlx::postgres::PgPool::connect(db_url.as_str()).await?;
88-
info!("Connected to PostgreSQL.");
89-
Ok(pool)
90-
}
91-
92-
// Creates a new redis pool
93-
fn create_redis_pool(redis_url: String) -> Result<deadpool_redis::Pool, deadpool_redis::CreatePoolError> {
94-
info!("Connecting to Redis...");
95-
let pool = Config::from_url(redis_url.as_str()).create_pool(Some(Runtime::Tokio1))?;
96-
info!("Connected to Redis.");
97-
Ok(pool)
98-
}

src/models/file.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub struct FileUploadForm {
2020
}
2121

2222
// A file in database
23-
#[derive(Serialize)]
23+
#[derive(Serialize, Deserialize)]
2424
pub struct DBFile {
2525
pub id: i32,
2626
pub filename: String,
@@ -30,13 +30,13 @@ pub struct DBFile {
3030
pub user_id: i32
3131
}
3232

33-
#[derive(Serialize)]
33+
#[derive(Serialize, Deserialize)]
3434
pub struct FileUploadResponse {
3535
pub file_id: i32,
3636
pub path: String
3737
}
3838

39-
#[derive(Serialize)]
39+
#[derive(Serialize, Deserialize)]
4040
pub struct FileShareResponse {
4141
pub code: String
4242
}
@@ -76,7 +76,7 @@ impl DBFile {
7676
}
7777
None => {
7878
debug!("File with ID [{}] foesn't exist", id);
79-
return Err(AppError::InternalServerError { msg: "File doesn't exist".to_string() })
79+
return Err(AppError::NotFound { msg: "File doesn't exist".to_string() })
8080
}
8181
}
8282
}

src/models/jwt.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ pub struct Claims {
1010
pub jti: String, // token id
1111
}
1212

13-
#[derive(Serialize)]
13+
#[derive(Serialize, Deserialize)]
1414
pub struct JwtTokenPair {
15-
access_token: String,
16-
refresh_token: String,
15+
pub access_token: String,
16+
pub refresh_token: String,
1717
}
1818

1919
#[derive(Serialize, Deserialize, PartialEq, Debug)]

src/models/user.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ pub struct DBUser {
1111

1212
// Essential user information.
1313
// To be used externally.
14-
#[derive(Serialize)]
14+
#[derive(Serialize, Deserialize)]
1515
pub struct UserInfo {
1616
pub id: i32,
1717
pub email: String,
1818
pub username: String,
1919
}
2020

21-
#[derive(Deserialize, Debug)]
21+
#[derive(Serialize, Deserialize, Debug)]
2222
pub struct UserLoginCredentials {
2323
pub email: String,
2424
pub password_hash: String,

src/routes/auth.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ async fn login(user: web::Json<UserLoginCredentials>, data: web::Data<AppState>)
9595
},
9696
None => {
9797
debug!("No user with credentials [{:?}]", user.into_inner());
98-
Err(AppError::BadRequest { msg: "No user found with given credentials".to_string() })
98+
Err(AppError::NotFound { msg: "No user found with given credentials".to_string() })
9999
}
100100
}
101101
}
@@ -120,23 +120,22 @@ async fn refresh_token(req: web::Json<RefreshRequest>, data: web::Data<AppState>
120120
}
121121

122122
// Check if token is blacklisted
123-
let mut conn = data.redis_pool
124-
.get().await
123+
let mut conn = data.redis_pool.get().await
125124
.map_err(|_| {
126125
warn!("Connection to Redis lost");
127126
AppError::InternalServerError { msg: "Connection to Redis lost".to_string() }
128127
})?;
129128

130129
// If token exists in Redis, it is blacklisted
131-
match conn.get::<_, Option<String>>(token.claims.jti.clone()).await.ok() {
132-
Some(_) => {
130+
match conn.get::<_, Option<String>>(token.claims.jti.clone()).await {
131+
Ok(Some(_)) => {
133132
debug!("Token is blacklisted: [{:?}]", req.0);
134133
Err(AppError::BadRequest { msg: "Token is blacklisted".to_string() })
135134
}
136-
None => {
135+
Ok(None) => {
137136
// Blacklist token.
138137
// Redis will delete this entry as soon as the token gets expired.
139-
conn.set_ex::<_, _, ()>(
138+
let _: () = conn.set_ex(
140139
token.claims.jti.clone(),
141140
req.refresh_token.clone(),
142141
// saturating_sub wraps to zero to prevent underflow
@@ -153,5 +152,9 @@ async fn refresh_token(req: web::Json<RefreshRequest>, data: web::Data<AppState>
153152
data.secret.clone()
154153
)))
155154
}
155+
Err(_) => {
156+
warn!("Faied to fetch token from Redis.");
157+
Err(AppError::InternalServerError {msg: "Faied to fetch token from Redis.".to_string()})
158+
}
156159
}
157160
}

src/routes/files.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,15 @@ async fn upload_file(
7777
let username_hash = hex::encode(hasher.finalize());
7878

7979
// Path to files storage
80-
let storage_path = std::env::var("FILES_STORAGE_PATH").unwrap();
81-
let path = format!("{}/{}/{}", storage_path, username_hash, form.json.filename.clone());
80+
let path = format!("{}/{}/{}", data.storage_dir, username_hash, form.json.filename.clone());
8281

8382
// Check if file already exists
8483
if Path::new(path.as_str()).exists() {
8584
debug!("File [{}] already exists", path);
8685
return Err(AppError::BadRequest { msg: "File with this name already exists".to_string() });
8786
}
8887

89-
let dir_path = format!("{}/{}", storage_path, username_hash);
88+
let dir_path = format!("{}/{}", data.storage_dir, username_hash);
9089

9190
// Create dirs for file of don't exist
9291
fs::create_dir_all(&dir_path).await
@@ -170,7 +169,7 @@ async fn download_shared_file(share_code: web::Path<String>, req: HttpRequest, d
170169
})?;
171170

172171
// Validate the share code
173-
let file_id = match conn.get::<_, Option<i32>>(share_code.clone()).await {
172+
let file_id: i32 = match conn.get(share_code.clone()).await {
174173
Ok(Some(id)) => {
175174
debug!("File ID valid: {}", id);
176175
id
@@ -268,7 +267,7 @@ async fn share_file(file_id: web::Path<i32>, req: HttpRequest, data: web::Data<A
268267
AppError::InternalServerError { msg: "Connection to Redis lost".to_string() }
269268
})?;
270269

271-
// Delete the previous code if exists
270+
// Delete the previous code if exists
272271
if let Ok(Some(old_code)) = conn.get::<_, Option<String>>(file_id).await {
273272
// Delete old code key
274273
let _: () = conn.del(&old_code).await.map_err(|_| {
@@ -295,15 +294,15 @@ async fn share_file(file_id: web::Path<i32>, req: HttpRequest, data: web::Data<A
295294
Some(_) => { /* Key already exists, continue */ },
296295
None => {
297296
// Code is unique, add it to redis with 5 minutes expiration time
298-
conn.set_ex::<_, _, ()>(key, file_id, 5 * 60).await
297+
let _:() = conn.set_ex(key, file_id, 5 * 60).await
299298
.map_err(|_| {
300299
warn!("Failed to save share code [{}]", share_code);
301300
AppError::InternalServerError { msg: "Failed to save share code".to_string() }
302301
})?;
303302

304303
// Reverse map, for accessing current share code for a file.
305304
// Overwrites previous data.
306-
conn.set_ex::<_, _, ()>(file_id, key, 5 * 60).await
305+
let _:() = conn.set_ex(file_id, key, 5 * 60).await
307306
.map_err(|_| {
308307
warn!("Failed to save reverse mapping for share code [{}]", share_code);
309308
AppError::InternalServerError { msg: "Failed to save reverse mapping for share code".to_string() }

src/utils/errors.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ pub enum AppError {
1515

1616
#[display("Internal server error: {}", msg)]
1717
InternalServerError { msg: String },
18+
19+
#[display("Not found: {}", msg)]
20+
NotFound { msg: String }
1821
}
1922

2023
#[derive(Serialize)]
@@ -28,6 +31,7 @@ impl ResponseError for AppError {
2831
AppError::Unauthorized => StatusCode::UNAUTHORIZED,
2932
AppError::BadRequest { .. } => StatusCode::BAD_REQUEST,
3033
AppError::InternalServerError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
34+
AppError::NotFound { .. } => StatusCode::NOT_FOUND
3135
}
3236
}
3337

0 commit comments

Comments
 (0)