add more invitation tests
This commit is contained in:
parent
c63786a3fc
commit
ba3e8625f6
2 changed files with 72 additions and 33 deletions
|
@ -15,8 +15,6 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||||
use super::{templates::*, Invitation};
|
use super::{templates::*, Invitation};
|
||||||
use crate::{util::empty_string_as_none, User};
|
use crate::{util::empty_string_as_none, User};
|
||||||
|
|
||||||
const ID_QUERY: &str = "select * from users where id = $1";
|
|
||||||
|
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
// Error types for user creation
|
// Error types for user creation
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
|
@ -147,6 +145,7 @@ pub async fn get_signup_success(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
State(pool): State<SqlitePool>,
|
State(pool): State<SqlitePool>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
|
const ID_QUERY: &str = "select * from users where id = ?";
|
||||||
let id = id.trim();
|
let id = id.trim();
|
||||||
let id = Julid::from_str(id).unwrap_or_default();
|
let id = Julid::from_str(id).unwrap_or_default();
|
||||||
let user: User = {
|
let user: User = {
|
||||||
|
@ -250,6 +249,14 @@ async fn validate_invitation(
|
||||||
if remaining < 1 {
|
if remaining < 1 {
|
||||||
return Err(CreateUserErrorKind::BadInvitation);
|
return Err(CreateUserErrorKind::BadInvitation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(ts) = invitation.expires_at {
|
||||||
|
let now = chrono::Utc::now().timestamp();
|
||||||
|
if ts < now {
|
||||||
|
return Err(CreateUserErrorKind::BadInvitation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let _ = sqlx::query("update invites set remaining = ? where id = ?")
|
let _ = sqlx::query("update invites set remaining = ? where id = ?")
|
||||||
.bind(remaining - 1)
|
.bind(remaining - 1)
|
||||||
.bind(invitation.id)
|
.bind(invitation.id)
|
||||||
|
@ -260,13 +267,6 @@ async fn validate_invitation(
|
||||||
CreateUserErrorKind::UnknownDBError
|
CreateUserErrorKind::UnknownDBError
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Some(ts) = invitation.expires_at {
|
|
||||||
let now = chrono::Utc::now().timestamp();
|
|
||||||
if ts < now {
|
|
||||||
return Err(CreateUserErrorKind::BadInvitation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(invitation.owner)
|
Ok(invitation.owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +383,7 @@ mod test {
|
||||||
fn used_up_invite() {
|
fn used_up_invite() {
|
||||||
let lucky1 = "username=lucky1&password=aaaa&pw_verify=aaaa&invitation=0000000000000000000000001A";
|
let lucky1 = "username=lucky1&password=aaaa&pw_verify=aaaa&invitation=0000000000000000000000001A";
|
||||||
let lucky2 = "username=lucky2&password=aaaa&pw_verify=aaaa&invitation=0000000000000000000000001A";
|
let lucky2 = "username=lucky2&password=aaaa&pw_verify=aaaa&invitation=0000000000000000000000001A";
|
||||||
let unlucky = "username=lucky3&password=aaaa&pw_verify=aaaa&invitation=0000000000000000000000001A";
|
let unlucky = "username=unlucky&password=aaaa&pw_verify=aaaa&invitation=0000000000000000000000001A";
|
||||||
let pool = get_db_pool();
|
let pool = get_db_pool();
|
||||||
let rt = Runtime::new().unwrap();
|
let rt = Runtime::new().unwrap();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
|
@ -444,8 +444,10 @@ mod test {
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_millis(100));
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
|
||||||
|
let username = "too slow";
|
||||||
|
|
||||||
let tooslow =
|
let tooslow =
|
||||||
format!("username=tooslow&password=aaaa&pw_verify=aaaa&invitation={invite}");
|
format!("username={username}&password=aaaa&pw_verify=aaaa&invitation={invite}");
|
||||||
let body = massage(&tooslow);
|
let body = massage(&tooslow);
|
||||||
|
|
||||||
let resp = server
|
let resp = server
|
||||||
|
@ -455,7 +457,7 @@ mod test {
|
||||||
.bytes(body)
|
.bytes(body)
|
||||||
.content_type(FORM_CONTENT_TYPE)
|
.content_type(FORM_CONTENT_TYPE)
|
||||||
.await;
|
.await;
|
||||||
let user = User::try_get("unlucky", &pool).await;
|
let user = User::try_get(username, &pool).await;
|
||||||
assert!(user.is_ok() && user.unwrap().is_none());
|
assert!(user.is_ok() && user.unwrap().is_none());
|
||||||
|
|
||||||
let body = String::from_utf8(resp.as_bytes().to_vec()).unwrap();
|
let body = String::from_utf8(resp.as_bytes().to_vec()).unwrap();
|
||||||
|
|
|
@ -9,17 +9,19 @@ pub mod templates;
|
||||||
|
|
||||||
#[Error(desc = "Could not create user.")]
|
#[Error(desc = "Could not create user.")]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
pub struct CreateInviteError(#[from] CreateInviteErrorKind);
|
pub struct CreateInviteError(#[from] CreateInviteErrorKind);
|
||||||
|
|
||||||
#[Error]
|
#[Error]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
pub enum CreateInviteErrorKind {
|
pub enum CreateInviteErrorKind {
|
||||||
DBError,
|
DBError,
|
||||||
NoOwner,
|
NoOwner,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
||||||
pub struct Invitation {
|
pub struct Invitation {
|
||||||
id: Julid,
|
id: Julid,
|
||||||
owner: Julid,
|
owner: Julid,
|
||||||
|
@ -27,6 +29,17 @@ pub struct Invitation {
|
||||||
remaining: i16,
|
remaining: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Invitation {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: 0.into(),
|
||||||
|
owner: 0.into(),
|
||||||
|
expires_at: None,
|
||||||
|
remaining: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Invitation {
|
impl Invitation {
|
||||||
pub async fn commit(&self, db: &SqlitePool) -> Result<Julid, CreateInviteError> {
|
pub async fn commit(&self, db: &SqlitePool) -> Result<Julid, CreateInviteError> {
|
||||||
sqlx::query_scalar(
|
sqlx::query_scalar(
|
||||||
|
@ -39,46 +52,70 @@ impl Invitation {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::debug!("Got error creating invite: {e}");
|
tracing::debug!("Got error creating invite: {e}");
|
||||||
match e {
|
if let sqlx::Error::Database(e) = e {
|
||||||
sqlx::Error::Database(dbe) => {
|
let exit = e.code().unwrap_or_default().parse().unwrap_or(0);
|
||||||
let exit = dbe.code().unwrap_or_default().parse().unwrap_or(0);
|
// https://www.sqlite.org/rescode.html#constraint_foreignkey
|
||||||
// https://www.sqlite.org/rescode.html#constraint_foreignkey
|
if exit == 787u32 {
|
||||||
if exit == 787u32 {
|
CreateInviteErrorKind::NoOwner
|
||||||
CreateInviteErrorKind::NoOwner
|
} else {
|
||||||
} else {
|
CreateInviteErrorKind::DBError
|
||||||
CreateInviteErrorKind::DBError
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => CreateInviteErrorKind::Unknown,
|
} else {
|
||||||
|
CreateInviteErrorKind::Unknown
|
||||||
}
|
}
|
||||||
})?
|
})?
|
||||||
.ok_or(CreateInviteErrorKind::DBError.into())
|
.ok_or(CreateInviteErrorKind::Unknown.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(owner: Julid) -> Self {
|
pub fn new(owner: Julid) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: Julid::alpha(), // stand-in value, will let the db fill it in
|
|
||||||
owner,
|
owner,
|
||||||
expires_at: None,
|
..Default::default()
|
||||||
remaining: 1,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_uses(&self, uses: u8) -> Self {
|
pub fn with_uses(&self, uses: u8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: self.id,
|
|
||||||
owner: self.owner,
|
|
||||||
expires_at: self.expires_at,
|
|
||||||
remaining: uses as i16,
|
remaining: uses as i16,
|
||||||
|
..*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_expires_in(&self, expires_in: Duration) -> Self {
|
pub fn with_expires_in(&self, expires_in: Duration) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: self.id,
|
|
||||||
owner: self.owner,
|
|
||||||
expires_at: Some((chrono::Utc::now() + expires_in).timestamp()),
|
expires_at: Some((chrono::Utc::now() + expires_in).timestamp()),
|
||||||
remaining: self.remaining,
|
..*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{get_db_pool, User};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_create() {
|
||||||
|
let pool = get_db_pool();
|
||||||
|
let rt = Runtime::new().unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
User::omega().try_insert(&pool).await.unwrap();
|
||||||
|
let invite = Invitation::new(Julid::omega());
|
||||||
|
invite.commit(&pool).await.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bad_owner() {
|
||||||
|
let pool = get_db_pool();
|
||||||
|
let rt = Runtime::new().unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
User::omega().try_insert(&pool).await.unwrap();
|
||||||
|
let invite = Invitation::new(Julid::alpha());
|
||||||
|
let res = invite.commit(&pool).await;
|
||||||
|
assert_eq!(res, Err(CreateInviteErrorKind::NoOwner.into()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue