diff --git a/Cargo.lock b/Cargo.lock index 82c9afa6f..a5194f393 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2755,6 +2755,7 @@ dependencies = [ "diesel", "diesel-async", "lemmy_db_schema", + "lemmy_utils", "pretty_assertions", "serde", "serde_with", diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts index 55a77d912..02fac3d9a 100644 --- a/api_tests/src/community.spec.ts +++ b/api_tests/src/community.spec.ts @@ -29,9 +29,7 @@ import { delta, betaAllowedInstances, searchPostLocal, - resolveBetaCommunity, longDelay, - delay, editCommunity, } from "./shared"; import { EditCommunity, EditSite } from "lemmy-js-client"; diff --git a/api_tests/src/image.spec.ts b/api_tests/src/image.spec.ts index e2a20808c..6414a8913 100644 --- a/api_tests/src/image.spec.ts +++ b/api_tests/src/image.spec.ts @@ -23,7 +23,6 @@ import { resolvePost, setupLogins, unfollowRemotes, - waitForPost, } from "./shared"; const downloadFileSync = require("download-file-sync"); diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index c5f91017e..dee6c43d6 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -23,7 +23,6 @@ import { unfollowRemotes, resolvePerson, banPersonFromSite, - searchPostLocal, followCommunity, banPersonFromCommunity, reportPost, @@ -38,10 +37,9 @@ import { alphaUrl, loginUser, createCommunity, - getPosts, } from "./shared"; import { PostView } from "lemmy-js-client/dist/types/PostView"; -import { EditSite, ListingType, ResolveObject } from "lemmy-js-client"; +import { EditSite, ResolveObject } from "lemmy-js-client"; let betaCommunity: CommunityView | undefined; @@ -239,12 +237,14 @@ test("Collection of featured posts gets federated", async () => { beta, community.community_view.community.actor_id, ); + expect(betaCommunity).toBeDefined(); const betaPost = await waitForPost( beta, post.post_view.post, post => post?.post.featured_community === true, ); + expect(betaPost).toBeDefined(); }); test("Lock a post", async () => { @@ -428,30 +428,34 @@ test("Search for a post", async () => { expect(betaPost?.post.name).toBeDefined(); }); -test("Enforce site ban for federated user", async () => { +test("Enforce site ban federation for local user", async () => { if (!betaCommunity) { throw "Missing beta community"; } + // create a test user - let alpha_user = await registerUser(alpha, alphaUrl); - let alphaUserPerson = (await getSite(alpha_user)).my_user?.local_user_view + let alphaUserHttp = await registerUser(alpha, alphaUrl); + let alphaUserPerson = (await getSite(alphaUserHttp)).my_user?.local_user_view .person; let alphaUserActorId = alphaUserPerson?.actor_id; if (!alphaUserActorId) { throw "Missing alpha user actor id"; } expect(alphaUserActorId).toBeDefined(); - let alphaPerson = (await resolvePerson(alpha_user, alphaUserActorId!)).person; + await followBeta(alphaUserHttp); + + let alphaPerson = (await resolvePerson(alphaUserHttp, alphaUserActorId!)) + .person; if (!alphaPerson) { throw "Missing alpha person"; } expect(alphaPerson).toBeDefined(); // alpha makes post in beta community, it federates to beta instance - let postRes1 = await createPost(alpha_user, betaCommunity.community.id); + let postRes1 = await createPost(alphaUserHttp, betaCommunity.community.id); let searchBeta1 = await waitForPost(beta, postRes1.post_view.post); - // ban alpha from its instance + // ban alpha from its own instance let banAlpha = await banPersonFromSite( alpha, alphaPerson.person.id, @@ -468,10 +472,11 @@ test("Enforce site ban for federated user", async () => { expect(alphaUserOnBeta1.person?.person.banned).toBe(true); // existing alpha post should be removed on beta - await waitUntil( + let betaBanRes = await waitUntil( () => getPost(beta, searchBeta1.post.id), s => s.post_view.post.removed, ); + expect(betaBanRes.post_view.post.removed).toBe(true); // Unban alpha let unBanAlpha = await banPersonFromSite( @@ -487,21 +492,84 @@ test("Enforce site ban for federated user", async () => { throw "Missing alpha person"; } let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name); - alpha_user.setHeaders({ + alphaUserHttp.setHeaders({ Authorization: "Bearer " + newAlphaUserJwt.jwt ?? "", }); // alpha makes new post in beta community, it federates - let postRes2 = await createPost(alpha_user, betaCommunity!.community.id); + let postRes2 = await createPost(alphaUserHttp, betaCommunity!.community.id); await waitForPost(beta, postRes2.post_view.post); - let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId!); - expect(alphaUserOnBeta2.person?.person.banned).toBe(false); + await unfollowRemotes(alpha); }); -test.skip("Enforce community ban for federated user", async () => { +test("Enforce site ban federation for federated user", async () => { if (!betaCommunity) { throw "Missing beta community"; } + + // create a test user + let alphaUserHttp = await registerUser(alpha, alphaUrl); + let alphaUserPerson = (await getSite(alphaUserHttp)).my_user?.local_user_view + .person; + let alphaUserActorId = alphaUserPerson?.actor_id; + if (!alphaUserActorId) { + throw "Missing alpha user actor id"; + } + expect(alphaUserActorId).toBeDefined(); + await followBeta(alphaUserHttp); + + let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId!); + expect(alphaUserOnBeta2.person?.person.banned).toBe(false); + + if (!alphaUserOnBeta2.person) { + throw "Missing alpha person"; + } + + // alpha makes post in beta community, it federates to beta instance + let postRes1 = await createPost(alphaUserHttp, betaCommunity.community.id); + let searchBeta1 = await waitForPost(beta, postRes1.post_view.post); + expect(searchBeta1.post).toBeDefined(); + + // Now ban and remove their data from beta + let banAlphaOnBeta = await banPersonFromSite( + beta, + alphaUserOnBeta2.person.person.id, + true, + true, + ); + expect(banAlphaOnBeta.banned).toBe(true); + + // The beta site ban should NOT be federated to alpha + let alphaPerson2 = (await getSite(alphaUserHttp)).my_user!.local_user_view + .person; + expect(alphaPerson2.banned).toBe(false); + + // existing alpha post should be removed on beta + let betaBanRes = await waitUntil( + () => getPost(beta, searchBeta1.post.id), + s => s.post_view.post.removed, + ); + expect(betaBanRes.post_view.post.removed).toBe(true); + + // existing alpha's post to the beta community should be removed on alpha + let alphaPostAfterRemoveOnBeta = await waitUntil( + () => getPost(alpha, postRes1.post_view.post.id), + s => s.post_view.post.removed, + ); + expect(betaBanRes.post_view.post.removed).toBe(true); + expect(alphaPostAfterRemoveOnBeta.post_view.post.removed).toBe(true); + expect( + alphaPostAfterRemoveOnBeta.post_view.creator_banned_from_community, + ).toBe(true); + + await unfollowRemotes(alpha); +}); + +test("Enforce community ban for federated user", async () => { + if (!betaCommunity) { + throw "Missing beta community"; + } + await followBeta(alpha); let alphaShortname = `@lemmy_alpha@lemmy-alpha:8541`; let alphaPerson = (await resolvePerson(beta, alphaShortname)).person; if (!alphaPerson) { @@ -511,38 +579,46 @@ test.skip("Enforce community ban for federated user", async () => { // make a post in beta, it goes through let postRes1 = await createPost(alpha, betaCommunity.community.id); - let searchBeta1 = await searchPostLocal(beta, postRes1.post_view.post); - expect(searchBeta1.posts[0]).toBeDefined(); + let searchBeta1 = await waitForPost(beta, postRes1.post_view.post); + expect(searchBeta1.post).toBeDefined(); // ban alpha from beta community let banAlpha = await banPersonFromCommunity( beta, alphaPerson.person.id, - 2, + searchBeta1.community.id, true, true, ); expect(banAlpha.banned).toBe(true); // ensure that the post by alpha got removed - await expect(getPost(alpha, searchBeta1.posts[0].post.id)).rejects.toBe( - Error("unknown"), + let removePostRes = await waitUntil( + () => getPost(alpha, postRes1.post_view.post.id), + s => s.post_view.post.removed, ); + expect(removePostRes.post_view.post.removed).toBe(true); + expect(removePostRes.post_view.creator_banned_from_community).toBe(true); + expect(removePostRes.community_view.banned_from_community).toBe(true); // Alpha tries to make post on beta, but it fails because of ban - await expect(createPost(alpha, betaCommunity.community.id)).rejects.toBe( - Error("banned_from_community"), - ); + await expect( + createPost(alpha, betaCommunity.community.id), + ).rejects.toStrictEqual(Error("banned_from_community")); // Unban alpha let unBanAlpha = await banPersonFromCommunity( beta, alphaPerson.person.id, - 2, + searchBeta1.community.id, false, false, ); expect(unBanAlpha.banned).toBe(false); + + // Need to re-follow the community + await followBeta(alpha); + let postRes3 = await createPost(alpha, betaCommunity.community.id); expect(postRes3.post_view.post).toBeDefined(); expect(postRes3.post_view.community.local).toBe(false); @@ -550,19 +626,25 @@ test.skip("Enforce community ban for federated user", async () => { expect(postRes3.post_view.counts.score).toBe(1); // Make sure that post makes it to beta community - let searchBeta2 = await searchPostLocal(beta, postRes3.post_view.post); - expect(searchBeta2.posts[0]).toBeDefined(); + let postRes4 = await waitForPost(beta, postRes3.post_view.post); + expect(postRes4.post).toBeDefined(); + expect(postRes4.creator_banned_from_community).toBe(false); + + await unfollowRemotes(alpha); }); test("A and G subscribe to B (center) A posts, it gets announced to G", async () => { if (!betaCommunity) { throw "Missing beta community"; } + await followBeta(alpha); + let postRes = await createPost(alpha, betaCommunity.community.id); expect(postRes.post_view.post).toBeDefined(); let betaPost = (await resolvePost(gamma, postRes.post_view.post)).post; expect(betaPost?.post.name).toBeDefined(); + await unfollowRemotes(alpha); }); test("Report a post", async () => { @@ -571,6 +653,7 @@ test("Report a post", async () => { if (!betaCommunity) { throw "Missing beta community"; } + await followBeta(alpha); let postRes = await createPost(beta, betaCommunity.community.id); expect(postRes.post_view.post).toBeDefined(); @@ -598,9 +681,11 @@ test("Report a post", async () => { expect(betaReport.original_post_url).toBe(alphaReport.original_post_url); expect(betaReport.original_post_body).toBe(alphaReport.original_post_body); expect(betaReport.reason).toBe(alphaReport.reason); + await unfollowRemotes(alpha); }); test("Fetch post via redirect", async () => { + await followBeta(alpha); let alphaPost = await createPost(alpha, betaCommunity!.community.id); expect(alphaPost.post_view.post).toBeDefined(); // Make sure that post is liked on beta @@ -621,4 +706,5 @@ test("Fetch post via redirect", async () => { let gammaPost = await gamma.resolveObject(form); expect(gammaPost).toBeDefined(); expect(gammaPost.post?.post.ap_id).toBe(alphaPost.post_view.post.ap_id); + await unfollowRemotes(alpha); }); diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index a71053199..af2629393 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -396,7 +396,7 @@ export async function banPersonFromSite( let form: BanPerson = { person_id, ban, - remove_data: remove_data, + remove_data, }; return api.banPerson(form); } diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index faa74824e..fda0bea6f 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -1,13 +1,29 @@ +use activitypub_federation::config::Data; use actix_web::{http::header::Header, HttpRequest}; use actix_web_httpauth::headers::authorization::{Authorization, Bearer}; use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine}; use captcha::Captcha; use lemmy_api_common::{ claims::Claims, + community::BanFromCommunity, context::LemmyContext, - utils::{check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME}, + send_activity::{ActivityChannel, SendActivityData}, + utils::{check_expire_time, check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME}, +}; +use lemmy_db_schema::{ + source::{ + community::{ + CommunityFollower, + CommunityFollowerForm, + CommunityPersonBan, + CommunityPersonBanForm, + }, + local_site::LocalSite, + moderator::{ModBanFromCommunity, ModBanFromCommunityForm}, + person::Person, + }, + traits::{Bannable, Crud, Followable}, }; -use lemmy_db_schema::source::local_site::LocalSite; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult}, @@ -141,6 +157,97 @@ pub(crate) fn build_totp_2fa( .with_lemmy_type(LemmyErrorType::CouldntGenerateTotp) } +/// Site bans are only federated for local users. +/// This is a problem, because site-banning non-local users will still leave content +/// they've posted to our local communities, on other servers. +/// +/// So when doing a site ban for a non-local user, you need to federate/send a +/// community ban for every local community they've participated in. +/// See https://github.com/LemmyNet/lemmy/issues/4118 +#[tracing::instrument(skip_all)] +pub(crate) async fn ban_nonlocal_user_from_local_communities( + local_user_view: &LocalUserView, + target: &Person, + ban: bool, + reason: &Option, + remove_data: &Option, + expires: &Option, + context: &Data, +) -> LemmyResult<()> { + // Only run this code for federated users + if !target.local { + let ids = Person::list_local_community_ids(&mut context.pool(), target.id).await?; + + for community_id in ids { + let expires_dt = check_expire_time(*expires)?; + + // Ban / unban them from our local communities + let community_user_ban_form = CommunityPersonBanForm { + community_id, + person_id: target.id, + expires: Some(expires_dt), + }; + + if ban { + // Ignore all errors for these + CommunityPersonBan::ban(&mut context.pool(), &community_user_ban_form) + .await + .ok(); + + // Also unsubscribe them from the community, if they are subscribed + let community_follower_form = CommunityFollowerForm { + community_id, + person_id: target.id, + pending: false, + }; + + CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) + .await + .ok(); + } else { + CommunityPersonBan::unban(&mut context.pool(), &community_user_ban_form) + .await + .ok(); + } + + // Mod tables + let form = ModBanFromCommunityForm { + mod_person_id: local_user_view.person.id, + other_person_id: target.id, + community_id, + reason: reason.clone(), + banned: Some(ban), + expires: expires_dt, + }; + + ModBanFromCommunity::create(&mut context.pool(), &form).await?; + + // Federate the ban from community + let ban_from_community = BanFromCommunity { + community_id, + person_id: target.id, + ban, + reason: reason.clone(), + remove_data: *remove_data, + expires: *expires, + }; + + ActivityChannel::submit_activity( + SendActivityData::BanFromCommunity { + moderator: local_user_view.person.clone(), + community_id, + target: target.clone(), + data: ban_from_community, + }, + context, + ) + .await?; + } + } + + Ok(()) +} + #[tracing::instrument(skip_all)] pub async fn local_user_view_from_jwt( jwt: &str, diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index c9fcfc4bf..f039c9a0c 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -1,3 +1,4 @@ +use crate::ban_nonlocal_user_from_local_communities; use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ @@ -47,7 +48,7 @@ pub async fn ban_from_site( .with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?; // if its a local user, invalidate logins - let local_user = LocalUserView::read_person(&mut context.pool(), data.person_id).await; + let local_user = LocalUserView::read_person(&mut context.pool(), person.id).await; if let Ok(local_user) = local_user { LoginToken::invalidate_all(&mut context.pool(), local_user.local_user.id).await?; } @@ -61,7 +62,7 @@ pub async fn ban_from_site( // Mod tables let form = ModBanForm { mod_person_id: local_user_view.person.id, - other_person_id: data.person_id, + other_person_id: person.id, reason: data.reason.clone(), banned: Some(data.ban), expires, @@ -69,7 +70,18 @@ pub async fn ban_from_site( ModBan::create(&mut context.pool(), &form).await?; - let person_view = PersonView::read(&mut context.pool(), data.person_id).await?; + let person_view = PersonView::read(&mut context.pool(), person.id).await?; + + ban_nonlocal_user_from_local_communities( + &local_user_view, + &person, + data.ban, + &data.reason, + &data.remove_data, + &data.expires, + &context, + ) + .await?; ActivityChannel::submit_activity( SendActivityData::BanFromSite { diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index dff2dffeb..32128a3c1 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -104,6 +104,7 @@ pub async fn search( users = PersonQuery { sort, search_term: (Some(q)), + listing_type: (listing_type), page: (page), limit: (limit), } @@ -174,6 +175,7 @@ pub async fn search( PersonQuery { sort, search_term: (Some(q)), + listing_type: (listing_type), page: (page), limit: (limit), } diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index c8a389475..979c3b721 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -1,6 +1,6 @@ use crate::{ newtypes::{CommentId, DbUrl, PersonId}, - schema::comment::dsl::{ap_id, comment, content, creator_id, deleted, path, removed, updated}, + schema::comment, source::comment::{ Comment, CommentInsertForm, @@ -30,11 +30,11 @@ impl Comment { ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - diesel::update(comment.filter(creator_id.eq(for_creator_id))) + diesel::update(comment::table.filter(comment::creator_id.eq(for_creator_id))) .set(( - content.eq(DELETED_REPLACEMENT_TEXT), - deleted.eq(true), - updated.eq(naive_now()), + comment::content.eq(DELETED_REPLACEMENT_TEXT), + comment::deleted.eq(true), + comment::updated.eq(naive_now()), )) .get_results::(conn) .await @@ -46,8 +46,11 @@ impl Comment { new_removed: bool, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - diesel::update(comment.filter(creator_id.eq(for_creator_id))) - .set((removed.eq(new_removed), updated.eq(naive_now()))) + diesel::update(comment::table.filter(comment::creator_id.eq(for_creator_id))) + .set(( + comment::removed.eq(new_removed), + comment::updated.eq(naive_now()), + )) .get_results::(conn) .await } @@ -64,9 +67,9 @@ impl Comment { .run(|conn| { Box::pin(async move { // Insert, to get the id - let inserted_comment = insert_into(comment) + let inserted_comment = insert_into(comment::table) .values(comment_form) - .on_conflict(ap_id) + .on_conflict(comment::ap_id) .do_update() .set(comment_form) .get_result::(conn) @@ -84,8 +87,8 @@ impl Comment { format!("{}.{}", 0, comment_id) }); - let updated_comment = diesel::update(comment.find(comment_id)) - .set(path.eq(ltree)) + let updated_comment = diesel::update(comment::table.find(comment_id)) + .set(comment::path.eq(ltree)) .get_result::(conn) .await?; @@ -133,8 +136,8 @@ where ca.comment_id = c.id" let conn = &mut get_conn(pool).await?; let object_id: DbUrl = object_id.into(); Ok( - comment - .filter(ap_id.eq(object_id)) + comment::table + .filter(comment::ap_id.eq(object_id)) .first::(conn) .await .ok() @@ -171,7 +174,7 @@ impl Crud for Comment { comment_form: &Self::UpdateForm, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(comment.find(comment_id)) + diesel::update(comment::table.find(comment_id)) .set(comment_form) .get_result::(conn) .await diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 9fb1ee1c5..0de7c32f5 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -1,6 +1,6 @@ use crate::{ newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, - schema::{instance, local_user, person, person_follower}, + schema::{comment, community, instance, local_user, person, person_follower, post}, source::person::{ Person, PersonFollower, @@ -11,7 +11,7 @@ use crate::{ traits::{ApubActor, Crud, Followable}, utils::{functions::lower, get_conn, naive_now, DbPool}, }; -use diesel::{dsl::insert_into, result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel::{dsl::insert_into, result::Error, CombineDsl, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_async::RunQueryDsl; #[async_trait] @@ -84,6 +84,29 @@ impl Person { .get_result::(conn) .await } + + /// Lists local community ids for all posts and comments for a given creator. + pub async fn list_local_community_ids( + pool: &mut DbPool<'_>, + for_creator_id: PersonId, + ) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + comment::table + .inner_join(post::table) + .inner_join(community::table.on(post::community_id.eq(community::id))) + .filter(community::local.eq(true)) + .filter(comment::creator_id.eq(for_creator_id)) + .select(community::id) + .union( + post::table + .inner_join(community::table) + .filter(community::local.eq(true)) + .filter(post::creator_id.eq(for_creator_id)) + .select(community::id), + ) + .load::(conn) + .await + } } impl PersonInsertForm { diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml index dd2b1aeb6..1892055d1 100644 --- a/crates/db_views_actor/Cargo.toml +++ b/crates/db_views_actor/Cargo.toml @@ -40,6 +40,7 @@ serial_test = { workspace = true } tokio = { workspace = true } pretty_assertions = { workspace = true } url.workspace = true +lemmy_utils.workspace = true [package.metadata.cargo-machete] ignored = ["strum"] diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs index 08ad2880f..aee56748e 100644 --- a/crates/db_views_actor/src/person_view.rs +++ b/crates/db_views_actor/src/person_view.rs @@ -23,6 +23,7 @@ use lemmy_db_schema::{ Queries, ReadFn, }, + ListingType, SortType, }; use serde::{Deserialize, Serialize}; @@ -115,6 +116,15 @@ fn queries<'a>( let (limit, offset) = limit_and_offset(options.page, options.limit)?; query = query.limit(limit).offset(offset); + + if let Some(listing_type) = options.listing_type { + query = match listing_type { + // return nothing as its not possible to follow users + ListingType::Subscribed => query.limit(0), + ListingType::Local => query.filter(person::local.eq(true)), + _ => query, + }; + } } } query.load::(&mut conn).await @@ -141,6 +151,7 @@ impl PersonView { pub struct PersonQuery { pub sort: Option, pub search_term: Option, + pub listing_type: Option, pub page: Option, pub limit: Option, } @@ -168,6 +179,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; @@ -178,64 +190,59 @@ mod tests { bob_local_user: LocalUser, } - async fn init_data(pool: &mut DbPool<'_>) -> Data { - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .unwrap(); + async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult { + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let alice_form = PersonInsertForm::builder() .name("alice".to_string()) .public_key("pubkey".to_string()) .instance_id(inserted_instance.id) + .local(Some(true)) .build(); - let alice = Person::create(pool, &alice_form).await.unwrap(); + let alice = Person::create(pool, &alice_form).await?; let alice_local_user_form = LocalUserInsertForm::builder() .person_id(alice.id) .password_encrypted(String::new()) .build(); - let alice_local_user = LocalUser::create(pool, &alice_local_user_form) - .await - .unwrap(); + let alice_local_user = LocalUser::create(pool, &alice_local_user_form).await?; let bob_form = PersonInsertForm::builder() .name("bob".to_string()) .bot_account(Some(true)) .public_key("pubkey".to_string()) .instance_id(inserted_instance.id) + .local(Some(false)) .build(); - let bob = Person::create(pool, &bob_form).await.unwrap(); + let bob = Person::create(pool, &bob_form).await?; let bob_local_user_form = LocalUserInsertForm::builder() .person_id(bob.id) .password_encrypted(String::new()) .build(); - let bob_local_user = LocalUser::create(pool, &bob_local_user_form).await.unwrap(); + let bob_local_user = LocalUser::create(pool, &bob_local_user_form).await?; - Data { + Ok(Data { alice, alice_local_user, bob, bob_local_user, - } + }) } - async fn cleanup(data: Data, pool: &mut DbPool<'_>) { - LocalUser::delete(pool, data.alice_local_user.id) - .await - .unwrap(); - LocalUser::delete(pool, data.bob_local_user.id) - .await - .unwrap(); - Person::delete(pool, data.alice.id).await.unwrap(); - Person::delete(pool, data.bob.id).await.unwrap(); - Instance::delete(pool, data.bob.instance_id).await.unwrap(); + async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> { + LocalUser::delete(pool, data.alice_local_user.id).await?; + LocalUser::delete(pool, data.bob_local_user.id).await?; + Person::delete(pool, data.alice.id).await?; + Person::delete(pool, data.bob.id).await?; + Instance::delete(pool, data.bob.instance_id).await?; + Ok(()) } #[tokio::test] #[serial] - async fn exclude_deleted() { + async fn exclude_deleted() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; Person::update( pool, @@ -245,8 +252,7 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; let read = PersonView::read(pool, data.alice.id).await; assert_eq!(read.err(), Some(NotFound)); @@ -256,20 +262,19 @@ mod tests { ..Default::default() } .list(pool) - .await - .unwrap(); + .await?; assert_length!(1, list); assert_eq!(list[0].person.id, data.bob.id); - cleanup(data, pool).await; + cleanup(data, pool).await } #[tokio::test] #[serial] - async fn list_banned() { + async fn list_banned() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; Person::update( pool, @@ -279,22 +284,21 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; - let list = PersonView::banned(pool).await.unwrap(); + let list = PersonView::banned(pool).await?; assert_length!(1, list); assert_eq!(list[0].person.id, data.alice.id); - cleanup(data, pool).await; + cleanup(data, pool).await } #[tokio::test] #[serial] - async fn list_admins() { + async fn list_admins() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; LocalUser::update( pool, @@ -304,22 +308,45 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; - let list = PersonView::admins(pool).await.unwrap(); + let list = PersonView::admins(pool).await?; assert_length!(1, list); assert_eq!(list[0].person.id, data.alice.id); - let is_admin = PersonView::read(pool, data.alice.id) - .await - .unwrap() - .is_admin; + let is_admin = PersonView::read(pool, data.alice.id).await?.is_admin; assert!(is_admin); - let is_admin = PersonView::read(pool, data.bob.id).await.unwrap().is_admin; + let is_admin = PersonView::read(pool, data.bob.id).await?.is_admin; assert!(!is_admin); - cleanup(data, pool).await; + cleanup(data, pool).await + } + + #[tokio::test] + #[serial] + async fn listing_type() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let data = init_data(pool).await?; + + let list = PersonQuery { + listing_type: Some(ListingType::Local), + ..Default::default() + } + .list(pool) + .await?; + assert_length!(1, list); + assert_eq!(list[0].person.id, data.alice.id); + + let list = PersonQuery { + listing_type: Some(ListingType::All), + ..Default::default() + } + .list(pool) + .await?; + assert_length!(2, list); + + cleanup(data, pool).await } }