mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-06-01 00:28:05 +00:00
600ae662a5
Since DB tests execute diesel migrations automatically, concurrent execution causes flaky failures from simultaneous migrations. This can be worked around using `cargo test --workspace -- --test-threads=1`, which is what the CI config does, but this is not intuitive for newcomer developers and unnecessarily slows down the test suite for the majority of tests which are safe to run concurrently. This fixes this issue by integrating with the small test crate `serial_test` and using it to explicitly mark DB tests to run sequentially while allowing all other tests to run in parallel. Additionally, this greatly improves the speed of `cargo test` by disabling doc-tests in all crates, since these are aren't currently used and cargo's doc-test pass, even when no doc-tests exist, has significant overhead. On my machine, this change significantly improves test suite times by about 85%, making it much more practical to develop with tools like `cargo watch` auto-running tests.
461 lines
11 KiB
Rust
461 lines
11 KiB
Rust
use crate::{is_email_regex, ApubObject, Crud, ToSafeSettings};
|
|
use bcrypt::{hash, DEFAULT_COST};
|
|
use diesel::{dsl::*, result::Error, *};
|
|
use lemmy_db_schema::{
|
|
naive_now,
|
|
schema::user_::dsl::*,
|
|
source::user::{UserForm, UserSafeSettings, User_},
|
|
Url,
|
|
};
|
|
use lemmy_utils::settings::Settings;
|
|
|
|
mod safe_type {
|
|
use crate::ToSafe;
|
|
use lemmy_db_schema::{schema::user_::columns::*, source::user::User_};
|
|
|
|
type Columns = (
|
|
id,
|
|
name,
|
|
preferred_username,
|
|
avatar,
|
|
admin,
|
|
banned,
|
|
published,
|
|
updated,
|
|
matrix_user_id,
|
|
actor_id,
|
|
bio,
|
|
local,
|
|
banner,
|
|
deleted,
|
|
inbox_url,
|
|
shared_inbox_url,
|
|
);
|
|
|
|
impl ToSafe for User_ {
|
|
type SafeColumns = Columns;
|
|
fn safe_columns_tuple() -> Self::SafeColumns {
|
|
(
|
|
id,
|
|
name,
|
|
preferred_username,
|
|
avatar,
|
|
admin,
|
|
banned,
|
|
published,
|
|
updated,
|
|
matrix_user_id,
|
|
actor_id,
|
|
bio,
|
|
local,
|
|
banner,
|
|
deleted,
|
|
inbox_url,
|
|
shared_inbox_url,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
mod safe_type_alias_1 {
|
|
use crate::ToSafe;
|
|
use lemmy_db_schema::{schema::user_alias_1::columns::*, source::user::UserAlias1};
|
|
|
|
type Columns = (
|
|
id,
|
|
name,
|
|
preferred_username,
|
|
avatar,
|
|
admin,
|
|
banned,
|
|
published,
|
|
updated,
|
|
matrix_user_id,
|
|
actor_id,
|
|
bio,
|
|
local,
|
|
banner,
|
|
deleted,
|
|
);
|
|
|
|
impl ToSafe for UserAlias1 {
|
|
type SafeColumns = Columns;
|
|
fn safe_columns_tuple() -> Self::SafeColumns {
|
|
(
|
|
id,
|
|
name,
|
|
preferred_username,
|
|
avatar,
|
|
admin,
|
|
banned,
|
|
published,
|
|
updated,
|
|
matrix_user_id,
|
|
actor_id,
|
|
bio,
|
|
local,
|
|
banner,
|
|
deleted,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
mod safe_type_alias_2 {
|
|
use crate::ToSafe;
|
|
use lemmy_db_schema::{schema::user_alias_2::columns::*, source::user::UserAlias2};
|
|
|
|
type Columns = (
|
|
id,
|
|
name,
|
|
preferred_username,
|
|
avatar,
|
|
admin,
|
|
banned,
|
|
published,
|
|
updated,
|
|
matrix_user_id,
|
|
actor_id,
|
|
bio,
|
|
local,
|
|
banner,
|
|
deleted,
|
|
);
|
|
|
|
impl ToSafe for UserAlias2 {
|
|
type SafeColumns = Columns;
|
|
fn safe_columns_tuple() -> Self::SafeColumns {
|
|
(
|
|
id,
|
|
name,
|
|
preferred_username,
|
|
avatar,
|
|
admin,
|
|
banned,
|
|
published,
|
|
updated,
|
|
matrix_user_id,
|
|
actor_id,
|
|
bio,
|
|
local,
|
|
banner,
|
|
deleted,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
mod safe_settings_type {
|
|
use crate::ToSafeSettings;
|
|
use lemmy_db_schema::{schema::user_::columns::*, source::user::User_};
|
|
|
|
type Columns = (
|
|
id,
|
|
name,
|
|
preferred_username,
|
|
email,
|
|
avatar,
|
|
admin,
|
|
banned,
|
|
published,
|
|
updated,
|
|
show_nsfw,
|
|
theme,
|
|
default_sort_type,
|
|
default_listing_type,
|
|
lang,
|
|
show_avatars,
|
|
send_notifications_to_email,
|
|
matrix_user_id,
|
|
actor_id,
|
|
bio,
|
|
local,
|
|
last_refreshed_at,
|
|
banner,
|
|
deleted,
|
|
);
|
|
|
|
impl ToSafeSettings for User_ {
|
|
type SafeSettingsColumns = Columns;
|
|
fn safe_settings_columns_tuple() -> Self::SafeSettingsColumns {
|
|
(
|
|
id,
|
|
name,
|
|
preferred_username,
|
|
email,
|
|
avatar,
|
|
admin,
|
|
banned,
|
|
published,
|
|
updated,
|
|
show_nsfw,
|
|
theme,
|
|
default_sort_type,
|
|
default_listing_type,
|
|
lang,
|
|
show_avatars,
|
|
send_notifications_to_email,
|
|
matrix_user_id,
|
|
actor_id,
|
|
bio,
|
|
local,
|
|
last_refreshed_at,
|
|
banner,
|
|
deleted,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait UserSafeSettings_ {
|
|
fn read(conn: &PgConnection, user_id: i32) -> Result<UserSafeSettings, Error>;
|
|
}
|
|
|
|
impl UserSafeSettings_ for UserSafeSettings {
|
|
fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
|
|
user_
|
|
.select(User_::safe_settings_columns_tuple())
|
|
.filter(deleted.eq(false))
|
|
.find(user_id)
|
|
.first::<Self>(conn)
|
|
}
|
|
}
|
|
|
|
impl Crud<UserForm> for User_ {
|
|
fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
|
|
user_
|
|
.filter(deleted.eq(false))
|
|
.find(user_id)
|
|
.first::<Self>(conn)
|
|
}
|
|
fn delete(conn: &PgConnection, user_id: i32) -> Result<usize, Error> {
|
|
diesel::delete(user_.find(user_id)).execute(conn)
|
|
}
|
|
fn create(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
|
|
insert_into(user_).values(form).get_result::<Self>(conn)
|
|
}
|
|
fn update(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result<Self, Error> {
|
|
diesel::update(user_.find(user_id))
|
|
.set(form)
|
|
.get_result::<Self>(conn)
|
|
}
|
|
}
|
|
|
|
impl ApubObject<UserForm> for User_ {
|
|
fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error> {
|
|
use lemmy_db_schema::schema::user_::dsl::*;
|
|
user_
|
|
.filter(deleted.eq(false))
|
|
.filter(actor_id.eq(object_id))
|
|
.first::<Self>(conn)
|
|
}
|
|
|
|
fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
|
|
insert_into(user_)
|
|
.values(user_form)
|
|
.on_conflict(actor_id)
|
|
.do_update()
|
|
.set(user_form)
|
|
.get_result::<Self>(conn)
|
|
}
|
|
}
|
|
|
|
pub trait User {
|
|
fn register(conn: &PgConnection, form: &UserForm) -> Result<User_, Error>;
|
|
fn update_password(conn: &PgConnection, user_id: i32, new_password: &str)
|
|
-> Result<User_, Error>;
|
|
fn read_from_name(conn: &PgConnection, from_user_name: &str) -> Result<User_, Error>;
|
|
fn add_admin(conn: &PgConnection, user_id: i32, added: bool) -> Result<User_, Error>;
|
|
fn ban_user(conn: &PgConnection, user_id: i32, ban: bool) -> Result<User_, Error>;
|
|
fn find_by_email_or_username(
|
|
conn: &PgConnection,
|
|
username_or_email: &str,
|
|
) -> Result<User_, Error>;
|
|
fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error>;
|
|
fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error>;
|
|
fn get_profile_url(&self, hostname: &str) -> String;
|
|
fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error>;
|
|
fn delete_account(conn: &PgConnection, user_id: i32) -> Result<User_, Error>;
|
|
}
|
|
|
|
impl User for User_ {
|
|
fn register(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
|
|
let mut edited_user = form.clone();
|
|
let password_hash =
|
|
hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
|
|
edited_user.password_encrypted = password_hash;
|
|
|
|
Self::create(&conn, &edited_user)
|
|
}
|
|
|
|
// TODO do more individual updates like these
|
|
fn update_password(conn: &PgConnection, user_id: i32, new_password: &str) -> Result<Self, Error> {
|
|
let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
|
|
|
|
diesel::update(user_.find(user_id))
|
|
.set((
|
|
password_encrypted.eq(password_hash),
|
|
updated.eq(naive_now()),
|
|
))
|
|
.get_result::<Self>(conn)
|
|
}
|
|
|
|
fn read_from_name(conn: &PgConnection, from_user_name: &str) -> Result<Self, Error> {
|
|
user_
|
|
.filter(local.eq(true))
|
|
.filter(deleted.eq(false))
|
|
.filter(name.eq(from_user_name))
|
|
.first::<Self>(conn)
|
|
}
|
|
|
|
fn add_admin(conn: &PgConnection, user_id: i32, added: bool) -> Result<Self, Error> {
|
|
diesel::update(user_.find(user_id))
|
|
.set(admin.eq(added))
|
|
.get_result::<Self>(conn)
|
|
}
|
|
|
|
fn ban_user(conn: &PgConnection, user_id: i32, ban: bool) -> Result<Self, Error> {
|
|
diesel::update(user_.find(user_id))
|
|
.set(banned.eq(ban))
|
|
.get_result::<Self>(conn)
|
|
}
|
|
|
|
fn find_by_email_or_username(
|
|
conn: &PgConnection,
|
|
username_or_email: &str,
|
|
) -> Result<Self, Error> {
|
|
if is_email_regex(username_or_email) {
|
|
Self::find_by_email(conn, username_or_email)
|
|
} else {
|
|
Self::find_by_username(conn, username_or_email)
|
|
}
|
|
}
|
|
|
|
fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error> {
|
|
user_
|
|
.filter(deleted.eq(false))
|
|
.filter(local.eq(true))
|
|
.filter(name.ilike(username))
|
|
.first::<User_>(conn)
|
|
}
|
|
|
|
fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error> {
|
|
user_
|
|
.filter(deleted.eq(false))
|
|
.filter(local.eq(true))
|
|
.filter(email.eq(from_email))
|
|
.first::<User_>(conn)
|
|
}
|
|
|
|
fn get_profile_url(&self, hostname: &str) -> String {
|
|
format!(
|
|
"{}://{}/u/{}",
|
|
Settings::get().get_protocol_string(),
|
|
hostname,
|
|
self.name
|
|
)
|
|
}
|
|
|
|
fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
|
|
diesel::update(user_.find(user_id))
|
|
.set((last_refreshed_at.eq(naive_now()),))
|
|
.get_result::<Self>(conn)
|
|
}
|
|
|
|
fn delete_account(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
|
|
diesel::update(user_.find(user_id))
|
|
.set((
|
|
preferred_username.eq::<Option<String>>(None),
|
|
email.eq::<Option<String>>(None),
|
|
matrix_user_id.eq::<Option<String>>(None),
|
|
bio.eq::<Option<String>>(None),
|
|
deleted.eq(true),
|
|
updated.eq(naive_now()),
|
|
))
|
|
.get_result::<Self>(conn)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{establish_unpooled_connection, source::user::*, ListingType, SortType};
|
|
use serial_test::serial;
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn test_crud() {
|
|
let conn = establish_unpooled_connection();
|
|
|
|
let new_user = UserForm {
|
|
name: "thommy".into(),
|
|
preferred_username: None,
|
|
password_encrypted: "nope".into(),
|
|
email: None,
|
|
matrix_user_id: None,
|
|
avatar: None,
|
|
banner: None,
|
|
admin: false,
|
|
banned: Some(false),
|
|
published: None,
|
|
updated: None,
|
|
show_nsfw: false,
|
|
theme: "browser".into(),
|
|
default_sort_type: SortType::Hot as i16,
|
|
default_listing_type: ListingType::Subscribed as i16,
|
|
lang: "browser".into(),
|
|
show_avatars: true,
|
|
send_notifications_to_email: false,
|
|
actor_id: None,
|
|
bio: None,
|
|
local: true,
|
|
private_key: None,
|
|
public_key: None,
|
|
last_refreshed_at: None,
|
|
inbox_url: None,
|
|
shared_inbox_url: None,
|
|
};
|
|
|
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
|
|
|
let expected_user = User_ {
|
|
id: inserted_user.id,
|
|
name: "thommy".into(),
|
|
preferred_username: None,
|
|
password_encrypted: "nope".into(),
|
|
email: None,
|
|
matrix_user_id: None,
|
|
avatar: None,
|
|
banner: None,
|
|
admin: false,
|
|
banned: false,
|
|
published: inserted_user.published,
|
|
updated: None,
|
|
show_nsfw: false,
|
|
theme: "browser".into(),
|
|
default_sort_type: SortType::Hot as i16,
|
|
default_listing_type: ListingType::Subscribed as i16,
|
|
lang: "browser".into(),
|
|
show_avatars: true,
|
|
send_notifications_to_email: false,
|
|
actor_id: inserted_user.actor_id.to_owned(),
|
|
bio: None,
|
|
local: true,
|
|
private_key: None,
|
|
public_key: None,
|
|
last_refreshed_at: inserted_user.published,
|
|
deleted: false,
|
|
inbox_url: inserted_user.inbox_url.to_owned(),
|
|
shared_inbox_url: None,
|
|
};
|
|
|
|
let read_user = User_::read(&conn, inserted_user.id).unwrap();
|
|
let updated_user = User_::update(&conn, inserted_user.id, &new_user).unwrap();
|
|
let num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
|
|
|
|
assert_eq!(expected_user, read_user);
|
|
assert_eq!(expected_user, inserted_user);
|
|
assert_eq!(expected_user, updated_user);
|
|
assert_eq!(1, num_deleted);
|
|
}
|
|
}
|