Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User route tests #57

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,26 @@ use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};

#[cfg(test)]
use crate::test_db::CustomConnectionManager;

mod errors;
mod room;
mod schema;
mod user;

#[cfg(test)]
mod test_helpers;
#[cfg(test)]
mod test_db;

#[cfg(test)]
pub type DbConnectionManager = CustomConnectionManager<PgConnection>;

#[cfg(not(test))]
pub type DbConnectionManager = ConnectionManager<PgConnection>;

pub type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
pub type Pool = r2d2::Pool<DbConnectionManager>;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
Expand All @@ -24,7 +35,7 @@ async fn main() -> std::io::Result<()> {
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");

// create db connection pool
let manager = ConnectionManager::<PgConnection>::new(database_url);
let manager = DbConnectionManager::new(database_url);
let pool: Pool = r2d2::Pool::builder()
.build(manager)
.expect("Failed to create pool.");
Expand Down
50 changes: 50 additions & 0 deletions server/src/test_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// See https://docs.diesel.rs/1.4.x/src/diesel/r2d2.rs.html
// We added the distinction between test and not test config to be able to use test_connections

use diesel::r2d2::{Error, ManageConnection};
use diesel::{Connection, PgConnection};
use diesel_migrations::run_pending_migrations;
use std::marker::PhantomData;

#[derive(Debug, Clone)]
pub struct CustomConnectionManager<T> {
database_url: String,
_marker: PhantomData<T>,
}

impl<T> CustomConnectionManager<T> {
/// Returns a new connection manager,
/// which establishes connections to the given database URL.
pub fn new<S: Into<String>>(database_url: S) -> Self {
CustomConnectionManager {
database_url: database_url.into(),
_marker: PhantomData,
}
}
}

unsafe impl<T: Send + 'static> Sync for CustomConnectionManager<T> {}

impl ManageConnection for CustomConnectionManager<PgConnection> {
type Connection = PgConnection;
type Error = diesel::r2d2::Error;

fn connect(&self) -> Result<PgConnection, Error> {
let conn = PgConnection::establish(&self.database_url)
.map_err(Error::ConnectionError)
.unwrap();
run_pending_migrations(&conn).unwrap();
conn.begin_test_transaction().unwrap();
Ok(conn)
}

fn is_valid(&self, conn: &mut PgConnection) -> Result<(), Error> {
conn.execute("SELECT 1")
.map(|_| ())
.map_err(Error::QueryError)
}

fn has_broken(&self, _conn: &mut PgConnection) -> bool {
false
}
}
23 changes: 23 additions & 0 deletions server/src/test_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
use crate::test_db::CustomConnectionManager;
use crate::room::{Room, RoomData};
use crate::user::{User, UserData};
use crate::Pool;
use actix_web::dev::{HttpServiceFactory, ServiceResponse};
use actix_web::test::TestRequest;
use actix_web::App;
use diesel::prelude::*;
use diesel_migrations::*;

pub fn connection_pool() -> Pool {
let database_url = dotenv::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set");
let manager = CustomConnectionManager::<PgConnection>::new(database_url);
return r2d2::Pool::builder()
.max_size(1)
.build(manager)
.expect("Failed to create pool.");
}

pub async fn test_request<F: HttpServiceFactory + 'static>(
pool: Pool,
service: F,
req: TestRequest,
) -> ServiceResponse {
let mut app = actix_web::test::init_service(App::new().data(pool).service(service)).await;
actix_web::test::call_service(&mut app, req.to_request()).await
}

pub fn connection() -> PgConnection {
let url = dotenv::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set");
let conn = PgConnection::establish(&url).unwrap();
Expand Down
12 changes: 10 additions & 2 deletions server/src/user/auth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::user::auth::AuthenticationError::{DatabaseError, UserNotFound};
use crate::user::{User, UserData, UserError};
use serde::{Deserialize, Serialize};
use diesel::PgConnection;
use pwhash::bcrypt;

Expand All @@ -11,6 +12,11 @@ pub enum AuthenticationError {
DatabaseError(UserError),
}

#[derive(Serialize, Deserialize, Debug)]
pub struct AuthToken {
pub token: String,
}

impl User {
pub fn generate_password(clear_password: &str) -> String {
bcrypt::hash(clear_password).unwrap()
Expand All @@ -23,7 +29,7 @@ impl User {
pub fn authenticate(
conn: &PgConnection,
user_data: UserData,
) -> Result<String, AuthenticationError> {
) -> Result<AuthToken, AuthenticationError> {
let user = User::_find_by_username(&conn, &*user_data.username);
let user = match user {
Err(e) => return Err(DatabaseError(e)),
Expand All @@ -34,7 +40,9 @@ impl User {
Some(u) => u,
};
if user.check_password(&*user_data.password) && user_data.username == user.username {
Ok(String::from("AUTH_TOKEN_NOT_IMPLEMENTED"))
Ok(AuthToken {
token: String::from("AUTH_TOKEN_NOT_IMPLEMENTED"),
})
} else {
Err(AuthenticationError::IncorrectPassword)
}
Expand Down
17 changes: 11 additions & 6 deletions server/src/user/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ pub struct UserData {
}

// Do not return passwords, write only the data we want to send out in this struct
#[derive(Serialize, Debug)]
#[derive(Serialize, Deserialize, Debug)]
pub struct UserResponse {
pub id: Uuid,
pub username: String,
pub created: NaiveDateTime,
pub updated: NaiveDateTime,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct UserListResponse {
pub users: Vec<UserResponse>,
}

#[derive(Debug)]
pub enum UserError {
UserNotFound,
Expand All @@ -41,15 +46,15 @@ pub enum UserError {
}

impl User {
pub fn find_all(conn: &PgConnection) -> Result<Vec<UserResponse>, UserError> {
pub fn find_all(conn: &PgConnection) -> Result<UserListResponse, UserError> {
use crate::schema::users::dsl::*;

let all_users = users.load::<User>(conn)?;
let items = all_users
.into_iter()
.map(UserResponse::from)
.collect::<Vec<UserResponse>>();
Ok(items)
Ok(UserListResponse { users: items })
}

pub fn find(conn: &PgConnection, user_id: Uuid) -> Result<Option<UserResponse>, UserError> {
Expand Down Expand Up @@ -215,7 +220,7 @@ mod tests {
fn find_all_returns_empty_list_when_no_users_exist() {
let conn = connection();

assert_eq!(User::find_all(&conn).unwrap().len(), 0);
assert_eq!(User::find_all(&conn).unwrap().users.len(), 0);
}

#[test]
Expand All @@ -227,8 +232,8 @@ mod tests {

let users = User::find_all(&conn).unwrap();

assert_eq!(users.len(), 2);
assert_ne!(users[0].id, users[1].id);
assert_eq!(users.users.len(), 2);
assert_ne!(users.users[0].id, users.users[1].id);
}

#[test]
Expand Down
5 changes: 2 additions & 3 deletions server/src/user/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use crate::user::model::{User, UserData};
use crate::Pool;
use actix_web::error::BlockingError;
use actix_web::{delete, get, post, put, web, HttpResponse};
use serde_json::json;
use uuid::Uuid;

#[get("/users")]
Expand All @@ -13,7 +12,7 @@ pub async fn list(pool: web::Data<Pool>) -> Result<HttpResponse, ServiceError> {
let users = web::block(move || User::find_all(&conn))
.await
.map_err(ServiceError::from)?;
Ok(HttpResponse::Ok().json(json!({ "users": users })))
Ok(HttpResponse::Ok().json(users))
}

#[get("/users/{id}")]
Expand Down Expand Up @@ -88,7 +87,7 @@ pub async fn authenticate(
let conn = pool.get().expect("couldn't get db connection from pool");
let auth_token = web::block(move || User::authenticate(&conn, user_data.into_inner())).await;
match auth_token {
Ok(token) => Ok(HttpResponse::Ok().json(json!({ "token": token }))),
Ok(token) => Ok(HttpResponse::Ok().json(token)),
Err(e) => match e {
BlockingError::Error(e) => Err(ServiceError::from(e)),
BlockingError::Canceled => Err(ServiceError::InternalServerError),
Expand Down