Db, add core user functionality and tests; db mod
Adds all the basic user functions, and tests for them. This is also intializes the DB Module, which will have more things in it
This commit is contained in:
parent
eb21bf0d51
commit
ce95a54227
4 changed files with 185 additions and 0 deletions
11
koucha/src/db.rs
Normal file
11
koucha/src/db.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
mod user;
|
||||||
|
pub use user::User;
|
||||||
|
|
||||||
|
macro_rules! define_key {
|
||||||
|
($name:ident) => {
|
||||||
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
|
pub struct $name(i64);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
define_key!(UserKey);
|
||||||
160
koucha/src/db/user.rs
Normal file
160
koucha/src/db/user.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
use crate::{
|
||||||
|
Result,
|
||||||
|
AdapterPool,
|
||||||
|
db::UserKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UnparsedUser {
|
||||||
|
pub id: i64,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
impl UnparsedUser {
|
||||||
|
pub fn parse(self) -> Result<User> {
|
||||||
|
Ok(User {
|
||||||
|
key: UserKey(self.id),
|
||||||
|
name: self.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct User {
|
||||||
|
key: UserKey,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
pub fn key(&self) -> UserKey { self.key }
|
||||||
|
pub fn name(&self) -> &str { &self.name }
|
||||||
|
|
||||||
|
pub async fn get(pool: &AdapterPool, key: UserKey) -> Result<Self> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
UnparsedUser,
|
||||||
|
"SELECT id, name FROM users WHERE id = ?",
|
||||||
|
key.0
|
||||||
|
).fetch_one(&pool.0).await?.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all(pool: &AdapterPool) -> Result<Vec<Self>> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
UnparsedUser,
|
||||||
|
"SELECT id, name FROM users"
|
||||||
|
).fetch_all(&pool.0).await?.into_iter().map(UnparsedUser::parse).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create(pool: &AdapterPool, name: &str) -> Result<Self> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
UnparsedUser,
|
||||||
|
"INSERT INTO users (name)
|
||||||
|
VALUES (?)
|
||||||
|
RETURNING id, name",
|
||||||
|
name
|
||||||
|
).fetch_one(&pool.0).await?.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_name(
|
||||||
|
pool: &AdapterPool, key: UserKey, new_name: &str
|
||||||
|
) -> Result<()> {
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE users SET name = ? WHERE id = ?",
|
||||||
|
new_name, key.0
|
||||||
|
).execute(&pool.0).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
test_utils::{
|
||||||
|
USERNAME, USERNAME2,
|
||||||
|
setup_adapter,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
const UID: i64 = 1;
|
||||||
|
let unparsed_user = UnparsedUser {
|
||||||
|
id: UID,
|
||||||
|
name: USERNAME.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let user = unparsed_user.parse().unwrap();
|
||||||
|
assert_eq!(user.key.0, UID);
|
||||||
|
assert_eq!(user.name, USERNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get() {
|
||||||
|
let adapter = setup_adapter().await;
|
||||||
|
let pool = adapter.get_pool();
|
||||||
|
let new_user = User::create(pool, USERNAME).await.unwrap();
|
||||||
|
|
||||||
|
let fetched_user = User::get(pool, new_user.key).await.unwrap();
|
||||||
|
assert_eq!(fetched_user.name, USERNAME);
|
||||||
|
assert_eq!(fetched_user.key.0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_all() {
|
||||||
|
let adapter = setup_adapter().await;
|
||||||
|
let pool = adapter.get_pool();
|
||||||
|
User::create(pool, USERNAME).await.unwrap();
|
||||||
|
User::create(pool, USERNAME2).await.unwrap();
|
||||||
|
|
||||||
|
let users = User::get_all(pool).await.unwrap();
|
||||||
|
assert_eq!(users.len(), 2);
|
||||||
|
assert!(users.iter().any(|u| u.name == USERNAME));
|
||||||
|
assert!(users.iter().any(|u| u.name == USERNAME2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn create_user() {
|
||||||
|
let adapter = setup_adapter().await;
|
||||||
|
let pool = adapter.get_pool();
|
||||||
|
|
||||||
|
let user = User::create(pool, USERNAME).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(user.name, USERNAME);
|
||||||
|
assert_eq!(user.key.0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn create_duplicate_user() {
|
||||||
|
let adapter = setup_adapter().await;
|
||||||
|
let pool = adapter.get_pool();
|
||||||
|
|
||||||
|
User::create(pool, USERNAME).await.unwrap();
|
||||||
|
let duplicate_user = User::create(pool, USERNAME).await;
|
||||||
|
|
||||||
|
assert!(duplicate_user.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_name() {
|
||||||
|
const NEW_USERNAME: &str = "Alicia";
|
||||||
|
assert!(NEW_USERNAME != USERNAME);
|
||||||
|
let adapter = setup_adapter().await;
|
||||||
|
let pool = adapter.get_pool();
|
||||||
|
|
||||||
|
let user = User::create(pool, USERNAME).await.unwrap();
|
||||||
|
User::update_name(pool, user.key, NEW_USERNAME).await.unwrap();
|
||||||
|
|
||||||
|
let updated = User::get(pool, user.key).await.unwrap();
|
||||||
|
assert_eq!(updated.name, NEW_USERNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_name_to_duplicate() {
|
||||||
|
let adapter = setup_adapter().await;
|
||||||
|
let pool = adapter.get_pool();
|
||||||
|
|
||||||
|
let user1 = User::create(pool, USERNAME).await.unwrap();
|
||||||
|
User::create(pool, USERNAME2).await.unwrap();
|
||||||
|
let status = User::update_name(pool, user1.key, USERNAME2).await;
|
||||||
|
|
||||||
|
assert!(status.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ use std::error::Error;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
|
pub mod db;
|
||||||
pub mod score;
|
pub mod score;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,24 @@
|
||||||
#![cfg(test)]
|
#![cfg(test)]
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Adapter,
|
||||||
|
AdapterBuilder,
|
||||||
|
};
|
||||||
use chrono::{
|
use chrono::{
|
||||||
Utc,
|
Utc,
|
||||||
TimeZone,
|
TimeZone,
|
||||||
DateTime
|
DateTime
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const USERNAME: &str = "Alice";
|
||||||
|
pub const USERNAME2: &str = "Bob";
|
||||||
|
|
||||||
pub fn get_datetime() -> DateTime<Utc> {
|
pub fn get_datetime() -> DateTime<Utc> {
|
||||||
Utc.with_ymd_and_hms(2020,1,1,0,0,0).unwrap()
|
Utc.with_ymd_and_hms(2020,1,1,0,0,0).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn setup_adapter() -> Adapter {
|
||||||
|
AdapterBuilder::new()
|
||||||
|
.database_url("sqlite::memory:")
|
||||||
|
.create().await.unwrap()
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue