127 lines
3.4 KiB
Rust
127 lines
3.4 KiB
Rust
use db::{Db, audit, users};
|
|
use domain::{AuditAction, AuditActor, Email, NewUser, Role};
|
|
use sqlx::PgPool;
|
|
|
|
fn new_user(email: &str, role: Role) -> NewUser {
|
|
NewUser {
|
|
email: Email::parse(email).unwrap(),
|
|
password_hash: "$argon2id$dummy".to_owned(),
|
|
role,
|
|
}
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn create_then_fetch_by_id_and_email(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let id = users::create_user(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&new_user("anna@example.com", Role::Admin),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let user = users::user_by_id(db.pool(), id).await.unwrap().unwrap();
|
|
assert_eq!(user.email.as_str(), "anna@example.com");
|
|
assert_eq!(user.role, Role::Admin);
|
|
|
|
let (by_email, hash) = users::credentials_by_email(db.pool(), "anna@example.com")
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(by_email.id, id);
|
|
assert_eq!(hash, "$argon2id$dummy");
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn create_user_audits_email_and_role_but_never_the_hash(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
let id = users::create_user(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&new_user("anna@example.com", Role::Editor),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let history = audit::history_for(db.pool(), "user", id.to_uuid())
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(history.len(), 1);
|
|
assert_eq!(history[0].action, AuditAction::Created);
|
|
let mut fields: Vec<&str> = history[0]
|
|
.changes
|
|
.iter()
|
|
.map(|c| c.field.as_str())
|
|
.collect();
|
|
fields.sort_unstable();
|
|
assert_eq!(fields, vec!["email", "role"]);
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn missing_email_returns_none(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
assert!(
|
|
users::credentials_by_email(db.pool(), "nobody@example.com")
|
|
.await
|
|
.unwrap()
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn list_users_is_ordered_by_email(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
users::create_user(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&new_user("zoe@example.com", Role::Editor),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
users::create_user(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&new_user("amy@example.com", Role::Admin),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
tx.commit().await.unwrap();
|
|
|
|
let users = users::list_users(db.pool()).await.unwrap();
|
|
let emails: Vec<&str> = users.iter().map(|u| u.email.as_str()).collect();
|
|
assert_eq!(emails, vec!["amy@example.com", "zoe@example.com"]);
|
|
}
|
|
|
|
#[sqlx::test]
|
|
async fn duplicate_email_is_rejected(pool: PgPool) {
|
|
let db = Db::from_pool(pool);
|
|
|
|
let mut tx = db.pool().begin().await.unwrap();
|
|
users::create_user(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&new_user("anna@example.com", Role::Admin),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
// Same normalized email again — the lower(email) unique index must reject it.
|
|
let err = users::create_user(
|
|
&mut tx,
|
|
AuditActor::System,
|
|
&new_user("anna@example.com", Role::Editor),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert!(
|
|
matches!(err, sqlx::Error::Database(_)),
|
|
"expected a unique-violation database error, got {err:?}"
|
|
);
|
|
}
|