241 lines
6.6 KiB
Rust
241 lines
6.6 KiB
Rust
use super::Database;
|
|
use crate::db::{errors::DataError, Users};
|
|
use crate::error::BotError;
|
|
use crate::models::{AccountStatus, User};
|
|
use async_trait::async_trait;
|
|
use std::convert::From;
|
|
|
|
#[derive(Eq, PartialEq, Debug, Default, sqlx::FromRow)]
|
|
struct UserRow {
|
|
pub username: String,
|
|
pub password: Option<String>,
|
|
pub active_room: Option<String>,
|
|
pub account_status: Option<AccountStatus>,
|
|
}
|
|
|
|
impl From<UserRow> for User {
|
|
fn from(row: UserRow) -> Self {
|
|
User {
|
|
username: row.username,
|
|
password: row.password,
|
|
active_room: row.active_room,
|
|
account_status: row.account_status.unwrap_or_default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Users for Database {
|
|
async fn upsert_user(&self, user: &User) -> Result<(), DataError> {
|
|
sqlx::query(
|
|
r#"INSERT INTO accounts (user_id, password) VALUES (?, ?)
|
|
ON CONFLICT(user_id) DO UPDATE SET password = ?"#,
|
|
)
|
|
.bind(&user.username)
|
|
.bind(&user.password)
|
|
.bind(&user.password)
|
|
.execute(&self.conn)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn delete_user(&self, username: &str) -> Result<(), DataError> {
|
|
sqlx::query(r#"DELETE FROM accounts WHERE user_id = ?"#)
|
|
.bind(&username)
|
|
.execute(&self.conn)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_user(&self, username: &str) -> Result<Option<User>, DataError> {
|
|
// Should be query_as! macro, but the left join breaks it with a
|
|
// non existing error message.
|
|
let user_row: Option<UserRow> = sqlx::query_as(
|
|
r#"SELECT
|
|
a.user_id as "username",
|
|
a.password,
|
|
s.active_room,
|
|
s.account_status
|
|
FROM accounts a
|
|
LEFT JOIN user_state s on a.user_id = s.user_id
|
|
WHERE a.user_id = ?"#,
|
|
)
|
|
.bind(username)
|
|
.fetch_optional(&self.conn)
|
|
.await?;
|
|
|
|
Ok(user_row.map(|r| r.into()))
|
|
}
|
|
|
|
async fn authenticate_user(
|
|
&self,
|
|
username: &str,
|
|
raw_password: &str,
|
|
) -> Result<Option<User>, BotError> {
|
|
let user = self.get_user(username).await?;
|
|
println!(
|
|
"user pw is {:?}",
|
|
user.as_ref().map(|u| u.password.as_ref())
|
|
);
|
|
Ok(user.filter(|u| u.verify_password(raw_password)))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::db::sqlite::Database;
|
|
use crate::db::Users;
|
|
|
|
//TODO test selecting user when state doesn't exist.
|
|
async fn create_db() -> Database {
|
|
let db_path = tempfile::NamedTempFile::new_in(".").unwrap();
|
|
crate::db::sqlite::migrator::migrate(db_path.path().to_str().unwrap())
|
|
.await
|
|
.unwrap();
|
|
|
|
Database::new(db_path.path().to_str().unwrap())
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn create_and_get_user_test() {
|
|
let db = create_db().await;
|
|
|
|
let insert_result = db
|
|
.upsert_user(&User {
|
|
username: "myuser".to_string(),
|
|
password: "abc".to_string(),
|
|
})
|
|
.await;
|
|
|
|
assert!(insert_result.is_ok());
|
|
|
|
let user = db
|
|
.get_user("myuser")
|
|
.await
|
|
.expect("User retrieval query failed");
|
|
|
|
assert!(user.is_some());
|
|
let user = user.unwrap();
|
|
assert_eq!(user.username, "myuser");
|
|
assert_eq!(user.password, "abc");
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn can_update_user() {
|
|
let db = create_db().await;
|
|
|
|
let insert_result1 = db
|
|
.upsert_user(&User {
|
|
username: "myuser".to_string(),
|
|
password: "abc".to_string(),
|
|
})
|
|
.await;
|
|
|
|
assert!(insert_result1.is_ok());
|
|
|
|
let insert_result2 = db
|
|
.upsert_user(&User {
|
|
username: "myuser".to_string(),
|
|
password: "123".to_string(),
|
|
})
|
|
.await;
|
|
|
|
assert!(insert_result2.is_ok());
|
|
|
|
let user = db
|
|
.get_user("myuser")
|
|
.await
|
|
.expect("User retrieval query failed");
|
|
|
|
assert!(user.is_some());
|
|
let user = user.unwrap();
|
|
assert_eq!(user.username, "myuser");
|
|
assert_eq!(user.password, "123"); //From second upsert
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn can_delete_user() {
|
|
let db = create_db().await;
|
|
|
|
let insert_result = db
|
|
.upsert_user(&User {
|
|
username: "myuser".to_string(),
|
|
password: "abc".to_string(),
|
|
})
|
|
.await;
|
|
|
|
assert!(insert_result.is_ok());
|
|
|
|
db.delete_user("myuser")
|
|
.await
|
|
.expect("User deletion query failed");
|
|
|
|
let user = db
|
|
.get_user("myuser")
|
|
.await
|
|
.expect("User retrieval query failed");
|
|
|
|
assert!(user.is_none());
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn username_not_in_db_returns_none() {
|
|
let db = create_db().await;
|
|
let user = db
|
|
.get_user("does not exist")
|
|
.await
|
|
.expect("Get user query failure");
|
|
|
|
assert!(user.is_none());
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn authenticate_user_is_some_with_valid_password() {
|
|
let db = create_db().await;
|
|
|
|
let insert_result = db
|
|
.upsert_user(&User {
|
|
username: "myuser".to_string(),
|
|
password: crate::logic::hash_password("abc").expect("password hash error!"),
|
|
})
|
|
.await;
|
|
|
|
assert!(insert_result.is_ok());
|
|
|
|
let user = db
|
|
.authenticate_user("myuser", "abc")
|
|
.await
|
|
.expect("User retrieval query failed");
|
|
|
|
assert!(user.is_some());
|
|
let user = user.unwrap();
|
|
assert_eq!(user.username, "myuser");
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn authenticate_user_is_none_with_wrong_password() {
|
|
let db = create_db().await;
|
|
|
|
let insert_result = db
|
|
.upsert_user(&User {
|
|
username: "myuser".to_string(),
|
|
password: crate::logic::hash_password("abc").expect("password hash error!"),
|
|
})
|
|
.await;
|
|
|
|
assert!(insert_result.is_ok());
|
|
|
|
let user = db
|
|
.authenticate_user("myuser", "wrong-password")
|
|
.await
|
|
.expect("User retrieval query failed");
|
|
|
|
assert!(user.is_none());
|
|
}
|
|
}
|