test(sandbox): add repository testing

This commit is contained in:
Matthieu Bessat 2025-10-12 14:02:20 +02:00
parent 3d93beb5bd
commit bbec0fc3bf
15 changed files with 545 additions and 124 deletions

10
lib/sandbox/src/db.rs Normal file
View file

@ -0,0 +1,10 @@
use fully_pub::fully_pub;
use sqlx::{
Pool, Sqlite,
};
/// database storage interface
#[fully_pub]
#[derive(Clone, Debug)]
struct Database(Pool<Sqlite>);

3
lib/sandbox/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod repositories;
pub mod db;
pub mod models;

View file

@ -1,4 +1,6 @@
pub mod models;
pub mod repositories;
pub mod db;
fn main() {
println!("Sandbox")

View file

@ -0,0 +1,18 @@
CREATE TABLE usersss (
id TEXT NOT NULL PRIMARY KEY,
handle TEXT NOT NULL UNIQUE,
full_name TEXT,
prefered_color INTEGER,
last_login_at DATETIME,
status TEXT NOT NULL,
groups TEXT NOT NULL,
avatar_bytes BLOB NOT NULL
);
CREATE TABLE user_tokens (
id TEXT NOT NULL PRIMARY KEY,
user_id TEXT NOT NULL,
secret TEXT NOT NULL,
last_use_time DATETIME,
creation_time DATETIME NOT NULL,
expiration_time DATETIME NOT NULL
);

View file

@ -0,0 +1 @@
pub mod user;

View file

@ -5,6 +5,7 @@ use fully_pub::fully_pub;
use sqlxgentools_attrs::{sql_generator_model, SqlGeneratorDerive};
#[derive(sqlx::Type, Clone, Debug, PartialEq)]
#[fully_pub]
enum UserStatus {
Disabled,
Invited,
@ -34,7 +35,7 @@ struct User {
struct UserToken {
#[sql_generator_field(is_primary=true)]
id: String,
#[sql_generator_field(foreign_key=Relation::BelongsTo(User))]
// #[sql_generator_field(foreign_key=Relation::BelongsTo(User))]
user_id: String,
secret: String,
last_use_time: Option<DateTime<Utc>>,
@ -42,4 +43,3 @@ struct UserToken {
expiration_time: DateTime<Utc>
}

View file

@ -0,0 +1,2 @@
pub mod user_repository;
pub mod user_token_repository;

View file

@ -0,0 +1,115 @@
use crate::models::user::User;
use crate::db::Database;
pub struct UserRepository {
db: Database,
}
impl UserRepository {
pub fn new(db: Database) -> Self {
UserRepository { db }
}
pub async fn get_all(&self) -> Result<Vec<User>, sqlx::Error> {
sqlx::query_as::<_, User>("SELECT * FROM usersss").fetch_all(&self.db.0).await
}
pub async fn get_by_id(&self, item_id: &str) -> Result<User, sqlx::Error> {
sqlx::query_as::<_, User>("SELECT * FROM usersss WHERE id = $1")
.bind(item_id)
.fetch_one(&self.db.0)
.await
}
pub async fn get_many_by_id(
&self,
items_ids: &[&str],
) -> Result<Vec<User>, sqlx::Error> {
if items_ids.is_empty() {
return Ok(vec![]);
}
let placeholder_params: String = (1..=(items_ids.len()))
.map(|i| format!("${}", i))
.collect::<Vec<String>>()
.join(",");
let query_sql = format!(
"SELECT * FROM usersss WHERE id IN ({})", placeholder_params
);
let mut query = sqlx::query_as::<_, User>(&query_sql);
for id in items_ids {
query = query.bind(id);
}
query.fetch_all(&self.db.0).await
}
pub async fn insert(&self, entity: &User) -> Result<(), sqlx::Error> {
sqlx::query(
"INSERT INTO usersss (id, handle, full_name, prefered_color, last_login_at, status, groups, avatar_bytes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
)
.bind(&entity.id)
.bind(&entity.handle)
.bind(&entity.full_name)
.bind(&entity.prefered_color)
.bind(&entity.last_login_at)
.bind(&entity.status)
.bind(&entity.groups)
.bind(&entity.avatar_bytes)
.execute(&self.db.0)
.await?;
Ok(())
}
pub async fn insert_many(&self, entities: &Vec<User>) -> Result<(), sqlx::Error> {
let values_templates: String = (1..(8usize * entities.len() + 1))
.collect::<Vec<usize>>()
.chunks(8usize)
.map(|c| c.to_vec())
.map(|x| {
format!(
"({})", x.iter().map(| i | format!("${}", i)).collect:: < Vec <
String >> ().join(", ")
)
})
.collect::<Vec<String>>()
.join(", ");
let query_sql = format!(
"INSERT INTO usersss (id, handle, full_name, prefered_color, last_login_at, status, groups, avatar_bytes) VALUES {} ON CONFLICT DO NOTHING",
values_templates
);
let mut query = sqlx::query(&query_sql);
for entity in entities {
query = query
.bind(&entity.id)
.bind(&entity.handle)
.bind(&entity.full_name)
.bind(&entity.prefered_color)
.bind(&entity.last_login_at)
.bind(&entity.status)
.bind(&entity.groups)
.bind(&entity.avatar_bytes);
}
query.execute(&self.db.0).await?;
Ok(())
}
pub async fn update_by_id(
&self,
item_id: &str,
entity: &User,
) -> Result<(), sqlx::Error> {
sqlx::query(
"UPDATE usersss SET id = $2, handle = $3, full_name = $4, prefered_color = $5, last_login_at = $6, status = $7, groups = $8, avatar_bytes = $9 WHERE id = $1",
)
.bind(item_id)
.bind(&entity.id)
.bind(&entity.handle)
.bind(&entity.full_name)
.bind(&entity.prefered_color)
.bind(&entity.last_login_at)
.bind(&entity.status)
.bind(&entity.groups)
.bind(&entity.avatar_bytes)
.execute(&self.db.0)
.await?;
Ok(())
}
pub async fn delete_by_id(&self, item_id: &str) -> Result<(), sqlx::Error> {
sqlx::query("DELETE FROM usersss WHERE id = $1")
.bind(item_id)
.execute(&self.db.0)
.await?;
Ok(())
}
}

View file

@ -0,0 +1,114 @@
use crate::models::user::UserToken;
use crate::db::Database;
pub struct UserTokenRepository {
db: Database,
}
impl UserTokenRepository {
pub fn new(db: Database) -> Self {
UserTokenRepository { db }
}
pub async fn get_all(&self) -> Result<Vec<UserToken>, sqlx::Error> {
sqlx::query_as::<_, UserToken>("SELECT * FROM user_tokens")
.fetch_all(&self.db.0)
.await
}
pub async fn get_by_id(&self, item_id: &str) -> Result<UserToken, sqlx::Error> {
sqlx::query_as::<_, UserToken>("SELECT * FROM user_tokens WHERE id = $1")
.bind(item_id)
.fetch_one(&self.db.0)
.await
}
pub async fn get_many_by_id(
&self,
items_ids: &[&str],
) -> Result<Vec<UserToken>, sqlx::Error> {
if items_ids.is_empty() {
return Ok(vec![]);
}
let placeholder_params: String = (1..=(items_ids.len()))
.map(|i| format!("${}", i))
.collect::<Vec<String>>()
.join(",");
let query_sql = format!(
"SELECT * FROM user_tokens WHERE id IN ({})", placeholder_params
);
let mut query = sqlx::query_as::<_, UserToken>(&query_sql);
for id in items_ids {
query = query.bind(id);
}
query.fetch_all(&self.db.0).await
}
pub async fn insert(&self, entity: &UserToken) -> Result<(), sqlx::Error> {
sqlx::query(
"INSERT INTO user_tokens (id, user_id, secret, last_use_time, creation_time, expiration_time) VALUES ($1, $2, $3, $4, $5, $6)",
)
.bind(&entity.id)
.bind(&entity.user_id)
.bind(&entity.secret)
.bind(&entity.last_use_time)
.bind(&entity.creation_time)
.bind(&entity.expiration_time)
.execute(&self.db.0)
.await?;
Ok(())
}
pub async fn insert_many(
&self,
entities: &Vec<UserToken>,
) -> Result<(), sqlx::Error> {
let values_templates: String = (1..(6usize * entities.len() + 1))
.collect::<Vec<usize>>()
.chunks(6usize)
.map(|c| c.to_vec())
.map(|x| {
format!(
"({})", x.iter().map(| i | format!("${}", i)).collect:: < Vec <
String >> ().join(", ")
)
})
.collect::<Vec<String>>()
.join(", ");
let query_sql = format!(
"INSERT INTO user_tokens (id, user_id, secret, last_use_time, creation_time, expiration_time) VALUES {} ON CONFLICT DO NOTHING",
values_templates
);
let mut query = sqlx::query(&query_sql);
for entity in entities {
query = query
.bind(&entity.id)
.bind(&entity.user_id)
.bind(&entity.secret)
.bind(&entity.last_use_time)
.bind(&entity.creation_time)
.bind(&entity.expiration_time);
}
query.execute(&self.db.0).await?;
Ok(())
}
pub async fn update_by_id(
&self,
item_id: &str,
entity: &UserToken,
) -> Result<(), sqlx::Error> {
sqlx::query(
"UPDATE user_tokens SET id = $2, user_id = $3, secret = $4, last_use_time = $5, creation_time = $6, expiration_time = $7 WHERE id = $1",
)
.bind(item_id)
.bind(&entity.id)
.bind(&entity.user_id)
.bind(&entity.secret)
.bind(&entity.last_use_time)
.bind(&entity.creation_time)
.bind(&entity.expiration_time)
.execute(&self.db.0)
.await?;
Ok(())
}
pub async fn delete_by_id(&self, item_id: &str) -> Result<(), sqlx::Error> {
sqlx::query("DELETE FROM user_tokens WHERE id = $1")
.bind(item_id)
.execute(&self.db.0)
.await?;
Ok(())
}
}