# Pleroma: A lightweight social networking server # Copyright © 2017-2021 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.UserTest do alias Pleroma.Activity alias Pleroma.Builders.UserBuilder alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI use Pleroma.DataCase use Oban.Testing, repo: Pleroma.Repo import Pleroma.Factory import ExUnit.CaptureLog import Swoosh.TestAssertions setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok end setup do: clear_config([:instance, :account_activation_required]) describe "service actors" do test "returns updated invisible actor" do uri = "#{Pleroma.Web.Endpoint.url()}/relay" followers_uri = "#{uri}/followers" insert( :user, %{ nickname: "relay", invisible: false, local: true, ap_id: uri, follower_address: followers_uri } ) actor = User.get_or_create_service_actor_by_ap_id(uri, "relay") assert actor.invisible end test "returns relay user" do uri = "#{Pleroma.Web.Endpoint.url()}/relay" followers_uri = "#{uri}/followers" assert %User{ nickname: "relay", invisible: true, local: true, ap_id: ^uri, follower_address: ^followers_uri } = User.get_or_create_service_actor_by_ap_id(uri, "relay") assert capture_log(fn -> refute User.get_or_create_service_actor_by_ap_id("/relay", "relay") end) =~ "Cannot create service actor:" end test "returns invisible actor" do uri = "#{Pleroma.Web.Endpoint.url()}/internal/fetch-test" followers_uri = "#{uri}/followers" user = User.get_or_create_service_actor_by_ap_id(uri, "internal.fetch-test") assert %User{ nickname: "internal.fetch-test", invisible: true, local: true, ap_id: ^uri, follower_address: ^followers_uri } = user user2 = User.get_or_create_service_actor_by_ap_id(uri, "internal.fetch-test") assert user.id == user2.id end end describe "AP ID user relationships" do setup do {:ok, user: insert(:user)} end test "outgoing_relationships_ap_ids/1", %{user: user} do rel_types = [:block, :mute, :notification_mute, :reblog_mute, :inverse_subscription] ap_ids_by_rel = Enum.into( rel_types, %{}, fn rel_type -> rel_records = insert_list(2, :user_relationship, %{source: user, relationship_type: rel_type}) ap_ids = Enum.map(rel_records, fn rr -> Repo.preload(rr, :target).target.ap_id end) {rel_type, Enum.sort(ap_ids)} end ) assert ap_ids_by_rel[:block] == Enum.sort(User.blocked_users_ap_ids(user)) assert ap_ids_by_rel[:block] == Enum.sort(Enum.map(User.blocked_users(user), & &1.ap_id)) assert ap_ids_by_rel[:mute] == Enum.sort(User.muted_users_ap_ids(user)) assert ap_ids_by_rel[:mute] == Enum.sort(Enum.map(User.muted_users(user), & &1.ap_id)) assert ap_ids_by_rel[:notification_mute] == Enum.sort(User.notification_muted_users_ap_ids(user)) assert ap_ids_by_rel[:notification_mute] == Enum.sort(Enum.map(User.notification_muted_users(user), & &1.ap_id)) assert ap_ids_by_rel[:reblog_mute] == Enum.sort(User.reblog_muted_users_ap_ids(user)) assert ap_ids_by_rel[:reblog_mute] == Enum.sort(Enum.map(User.reblog_muted_users(user), & &1.ap_id)) assert ap_ids_by_rel[:inverse_subscription] == Enum.sort(User.subscriber_users_ap_ids(user)) assert ap_ids_by_rel[:inverse_subscription] == Enum.sort(Enum.map(User.subscriber_users(user), & &1.ap_id)) outgoing_relationships_ap_ids = User.outgoing_relationships_ap_ids(user, rel_types) assert ap_ids_by_rel == Enum.into(outgoing_relationships_ap_ids, %{}, fn {k, v} -> {k, Enum.sort(v)} end) end end describe "when tags are nil" do test "tagging a user" do user = insert(:user, %{tags: nil}) user = User.tag(user, ["cool", "dude"]) assert "cool" in user.tags assert "dude" in user.tags end test "untagging a user" do user = insert(:user, %{tags: nil}) user = User.untag(user, ["cool", "dude"]) assert user.tags == [] end end test "ap_id returns the activity pub id for the user" do user = UserBuilder.build() expected_ap_id = "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}" assert expected_ap_id == User.ap_id(user) end test "ap_followers returns the followers collection for the user" do user = UserBuilder.build() expected_followers_collection = "#{User.ap_id(user)}/followers" assert expected_followers_collection == User.ap_followers(user) end test "ap_following returns the following collection for the user" do user = UserBuilder.build() expected_followers_collection = "#{User.ap_id(user)}/following" assert expected_followers_collection == User.ap_following(user) end test "returns all pending follow requests" do unlocked = insert(:user) locked = insert(:user, is_locked: true) follower = insert(:user) CommonAPI.follow(follower, unlocked) CommonAPI.follow(follower, locked) assert [] = User.get_follow_requests(unlocked) assert [activity] = User.get_follow_requests(locked) assert activity end test "doesn't return already accepted or duplicate follow requests" do locked = insert(:user, is_locked: true) pending_follower = insert(:user) accepted_follower = insert(:user) CommonAPI.follow(pending_follower, locked) CommonAPI.follow(pending_follower, locked) CommonAPI.follow(accepted_follower, locked) Pleroma.FollowingRelationship.update(accepted_follower, locked, :follow_accept) assert [^pending_follower] = User.get_follow_requests(locked) end test "doesn't return follow requests for deactivated accounts" do locked = insert(:user, is_locked: true) pending_follower = insert(:user, %{is_active: false}) CommonAPI.follow(pending_follower, locked) refute pending_follower.is_active assert [] = User.get_follow_requests(locked) end test "clears follow requests when requester is blocked" do followed = insert(:user, is_locked: true) follower = insert(:user) CommonAPI.follow(follower, followed) assert [_activity] = User.get_follow_requests(followed) {:ok, _user_relationship} = User.block(followed, follower) assert [] = User.get_follow_requests(followed) end test "follow_all follows mutliple users" do user = insert(:user) followed_zero = insert(:user) followed_one = insert(:user) followed_two = insert(:user) blocked = insert(:user) not_followed = insert(:user) reverse_blocked = insert(:user) {:ok, _user_relationship} = User.block(user, blocked) {:ok, _user_relationship} = User.block(reverse_blocked, user) {:ok, user, followed_zero} = User.follow(user, followed_zero) {:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked, reverse_blocked]) assert User.following?(user, followed_one) assert User.following?(user, followed_two) assert User.following?(user, followed_zero) refute User.following?(user, not_followed) refute User.following?(user, blocked) refute User.following?(user, reverse_blocked) end test "follow_all follows mutliple users without duplicating" do user = insert(:user) followed_zero = insert(:user) followed_one = insert(:user) followed_two = insert(:user) {:ok, user} = User.follow_all(user, [followed_zero, followed_one]) assert length(User.following(user)) == 3 {:ok, user} = User.follow_all(user, [followed_one, followed_two]) assert length(User.following(user)) == 4 end test "follow takes a user and another user" do user = insert(:user) followed = insert(:user) {:ok, user, followed} = User.follow(user, followed) user = User.get_cached_by_id(user.id) followed = User.get_cached_by_ap_id(followed.ap_id) assert followed.follower_count == 1 assert user.following_count == 1 assert User.ap_followers(followed) in User.following(user) end test "can't follow a deactivated users" do user = insert(:user) followed = insert(:user, %{is_active: false}) {:error, _} = User.follow(user, followed) end test "can't follow a user who blocked us" do blocker = insert(:user) blockee = insert(:user) {:ok, _user_relationship} = User.block(blocker, blockee) {:error, _} = User.follow(blockee, blocker) end test "can't subscribe to a user who blocked us" do blocker = insert(:user) blocked = insert(:user) {:ok, _user_relationship} = User.block(blocker, blocked) {:error, _} = User.subscribe(blocked, blocker) end test "local users do not automatically follow local locked accounts" do follower = insert(:user, is_locked: true) followed = insert(:user, is_locked: true) {:ok, follower, followed} = User.maybe_direct_follow(follower, followed) refute User.following?(follower, followed) end describe "unfollow/2" do setup do: clear_config([:instance, :external_user_synchronization]) test "unfollow with synchronizes external user" do clear_config([:instance, :external_user_synchronization], true) followed = insert(:user, nickname: "fuser1", follower_address: "http://localhost:4001/users/fuser1/followers", following_address: "http://localhost:4001/users/fuser1/following", ap_id: "http://localhost:4001/users/fuser1" ) user = insert(:user, %{ local: false, nickname: "fuser2", ap_id: "http://remote.org/users/fuser2", follower_address: "http://remote.org/users/fuser2/followers", following_address: "http://remote.org/users/fuser2/following" }) {:ok, user, followed} = User.follow(user, followed, :follow_accept) {:ok, user, _activity} = User.unfollow(user, followed) user = User.get_cached_by_id(user.id) assert User.following(user) == [] end test "unfollow takes a user and another user" do followed = insert(:user) user = insert(:user) {:ok, user, followed} = User.follow(user, followed, :follow_accept) assert User.following(user) == [user.follower_address, followed.follower_address] {:ok, user, _activity} = User.unfollow(user, followed) assert User.following(user) == [user.follower_address] end test "unfollow doesn't unfollow yourself" do user = insert(:user) {:error, _} = User.unfollow(user, user) assert User.following(user) == [user.follower_address] end end test "test if a user is following another user" do followed = insert(:user) user = insert(:user) User.follow(user, followed, :follow_accept) assert User.following?(user, followed) refute User.following?(followed, user) end test "fetches correct profile for nickname beginning with number" do # Use old-style integer ID to try to reproduce the problem user = insert(:user, %{id: 1080}) user_with_numbers = insert(:user, %{nickname: "#{user.id}garbage"}) assert user_with_numbers == User.get_cached_by_nickname_or_id(user_with_numbers.nickname) end describe "user registration" do @full_user_data %{ bio: "A guy", name: "my name", nickname: "nick", password: "test", password_confirmation: "test", email: "email@example.com" } setup do: clear_config([:instance, :autofollowed_nicknames]) setup do: clear_config([:instance, :autofollowing_nicknames]) setup do: clear_config([:welcome]) setup do: clear_config([:instance, :account_activation_required]) test "it autofollows accounts that are set for it" do user = insert(:user) remote_user = insert(:user, %{local: false}) clear_config([:instance, :autofollowed_nicknames], [ user.nickname, remote_user.nickname ]) cng = User.register_changeset(%User{}, @full_user_data) {:ok, registered_user} = User.register(cng) assert User.following?(registered_user, user) refute User.following?(registered_user, remote_user) end test "it adds automatic followers for new registered accounts" do user1 = insert(:user) user2 = insert(:user) clear_config([:instance, :autofollowing_nicknames], [ user1.nickname, user2.nickname ]) cng = User.register_changeset(%User{}, @full_user_data) {:ok, registered_user} = User.register(cng) assert User.following?(user1, registered_user) assert User.following?(user2, registered_user) end test "it sends a welcome message if it is set" do welcome_user = insert(:user) clear_config([:welcome, :direct_message, :enabled], true) clear_config([:welcome, :direct_message, :sender_nickname], welcome_user.nickname) clear_config([:welcome, :direct_message, :message], "Hello, this is a direct message") cng = User.register_changeset(%User{}, @full_user_data) {:ok, registered_user} = User.register(cng) ObanHelpers.perform_all() activity = Repo.one(Pleroma.Activity) assert registered_user.ap_id in activity.recipients assert Object.normalize(activity, fetch: false).data["content"] =~ "direct message" assert activity.actor == welcome_user.ap_id end setup do: clear_config( [:mrf_simple], %{ media_removal: [], media_nsfw: [], federated_timeline_removal: [], report_removal: [], reject: [], followers_only: [], accept: [], avatar_removal: [], banner_removal: [], reject_deletes: [] } ) setup do: clear_config(:mrf, policies: [ Pleroma.Web.ActivityPub.MRF.SimplePolicy ] ) test "it sends a welcome email message if it is set" do welcome_user = insert(:user) clear_config([:welcome, :email, :enabled], true) clear_config([:welcome, :email, :sender], welcome_user.email) clear_config( [:welcome, :email, :subject], "Hello, welcome to cool site: <%= instance_name %>" ) instance_name = Pleroma.Config.get([:instance, :name]) cng = User.register_changeset(%User{}, @full_user_data) {:ok, registered_user} = User.register(cng) ObanHelpers.perform_all() assert_email_sent( from: {instance_name, welcome_user.email}, to: {registered_user.name, registered_user.email}, subject: "Hello, welcome to cool site: #{instance_name}", html_body: "Welcome to #{instance_name}" ) end test "it sends a confirm email" do clear_config([:instance, :account_activation_required], true) cng = User.register_changeset(%User{}, @full_user_data) {:ok, registered_user} = User.register(cng) ObanHelpers.perform_all() Pleroma.Emails.UserEmail.account_confirmation_email(registered_user) |> assert_email_sent() end test "sends a pending approval email" do clear_config([:instance, :account_approval_required], true) {:ok, user} = User.register_changeset(%User{}, @full_user_data) |> User.register() ObanHelpers.perform_all() assert_email_sent( from: Pleroma.Config.Helpers.sender(), to: {user.name, user.email}, subject: "Your account is awaiting approval" ) end test "it sends a registration confirmed email if no others will be sent" do clear_config([:welcome, :email, :enabled], false) clear_config([:instance, :account_activation_required], false) clear_config([:instance, :account_approval_required], false) {:ok, user} = User.register_changeset(%User{}, @full_user_data) |> User.register() ObanHelpers.perform_all() instance_name = Pleroma.Config.get([:instance, :name]) sender = Pleroma.Config.get([:instance, :notify_email]) assert_email_sent( from: {instance_name, sender}, to: {user.name, user.email}, subject: "Account registered on #{instance_name}" ) end test "it fails gracefully with invalid email config" do cng = User.register_changeset(%User{}, @full_user_data) # Disable the mailer but enable all the things that want to send emails clear_config([Pleroma.Emails.Mailer, :enabled], false) clear_config([:instance, :account_activation_required], true) clear_config([:instance, :account_approval_required], true) clear_config([:welcome, :email, :enabled], true) clear_config([:welcome, :email, :sender], "lain@lain.com") # The user is still created assert {:ok, %User{nickname: "nick"}} = User.register(cng) # No emails are sent ObanHelpers.perform_all() refute_email_sent() end test "it works when the registering user does not provide an email" do clear_config([Pleroma.Emails.Mailer, :enabled], false) clear_config([:instance, :account_activation_required], false) clear_config([:instance, :account_approval_required], true) cng = User.register_changeset(%User{}, @full_user_data |> Map.put(:email, "")) # The user is still created assert {:ok, %User{nickname: "nick"}} = User.register(cng) # No emails are sent ObanHelpers.perform_all() refute_email_sent() end test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do clear_config([:instance, :account_activation_required], true) @full_user_data |> Map.keys() |> Enum.each(fn key -> params = Map.delete(@full_user_data, key) changeset = User.register_changeset(%User{}, params) assert if key == :bio, do: changeset.valid?, else: not changeset.valid? end) end test "it requires an name, nickname and password, bio and email are optional when account_activation_required is disabled" do clear_config([:instance, :account_activation_required], false) @full_user_data |> Map.keys() |> Enum.each(fn key -> params = Map.delete(@full_user_data, key) changeset = User.register_changeset(%User{}, params) assert if key in [:bio, :email], do: changeset.valid?, else: not changeset.valid? end) end test "it restricts certain nicknames" do [restricted_name | _] = Pleroma.Config.get([User, :restricted_nicknames]) assert is_bitstring(restricted_name) params = @full_user_data |> Map.put(:nickname, restricted_name) changeset = User.register_changeset(%User{}, params) refute changeset.valid? end test "it blocks blacklisted email domains" do clear_config([User, :email_blacklist], ["trolling.world"]) # Block with match params = Map.put(@full_user_data, :email, "troll@trolling.world") changeset = User.register_changeset(%User{}, params) refute changeset.valid? # Block with subdomain match params = Map.put(@full_user_data, :email, "troll@gnomes.trolling.world") changeset = User.register_changeset(%User{}, params) refute changeset.valid? # Pass with different domains that are similar params = Map.put(@full_user_data, :email, "troll@gnomestrolling.world") changeset = User.register_changeset(%User{}, params) assert changeset.valid? params = Map.put(@full_user_data, :email, "troll@trolling.world.us") changeset = User.register_changeset(%User{}, params) assert changeset.valid? end test "it sets the password_hash, ap_id, private key and followers collection address" do changeset = User.register_changeset(%User{}, @full_user_data) assert changeset.valid? assert is_binary(changeset.changes[:password_hash]) assert is_binary(changeset.changes[:keys]) assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname}) assert is_binary(changeset.changes[:keys]) assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers" end test "it creates a confirmed user" do changeset = User.register_changeset(%User{}, @full_user_data) assert changeset.valid? {:ok, user} = Repo.insert(changeset) assert user.is_confirmed end end describe "user registration, with :account_activation_required" do @full_user_data %{ bio: "A guy", name: "my name", nickname: "nick", password: "test", password_confirmation: "test", email: "email@example.com" } setup do: clear_config([:instance, :account_activation_required], true) test "it creates unconfirmed user" do changeset = User.register_changeset(%User{}, @full_user_data) assert changeset.valid? {:ok, user} = Repo.insert(changeset) refute user.is_confirmed assert user.confirmation_token end test "it creates confirmed user if :confirmed option is given" do changeset = User.register_changeset(%User{}, @full_user_data, confirmed: true) assert changeset.valid? {:ok, user} = Repo.insert(changeset) assert user.is_confirmed refute user.confirmation_token end end describe "user registration, with :account_approval_required" do @full_user_data %{ bio: "A guy", name: "my name", nickname: "nick", password: "test", password_confirmation: "test", email: "email@example.com", registration_reason: "I'm a cool guy :)" } setup do: clear_config([:instance, :account_approval_required], true) test "it creates unapproved user" do changeset = User.register_changeset(%User{}, @full_user_data) assert changeset.valid? {:ok, user} = Repo.insert(changeset) refute user.is_approved assert user.registration_reason == "I'm a cool guy :)" end test "it restricts length of registration reason" do reason_limit = Pleroma.Config.get([:instance, :registration_reason_length]) assert is_integer(reason_limit) params = @full_user_data |> Map.put( :registration_reason, "Quia et nesciunt dolores numquam ipsam nisi sapiente soluta. Ullam repudiandae nisi quam porro officiis officiis ad. Consequatur animi velit ex quia. Odit voluptatem perferendis quia ut nisi. Dignissimos sit soluta atque aliquid dolorem ut dolorum ut. Labore voluptates iste iusto amet voluptatum earum. Ad fugit illum nam eos ut nemo. Pariatur ea fuga non aspernatur. Dignissimos debitis officia corporis est nisi ab et. Atque itaque alias eius voluptas minus. Accusamus numquam tempore occaecati in." ) changeset = User.register_changeset(%User{}, params) refute changeset.valid? end end describe "get_or_fetch/1" do test "gets an existing user by nickname" do user = insert(:user) {:ok, fetched_user} = User.get_or_fetch(user.nickname) assert user == fetched_user end test "gets an existing user by ap_id" do ap_id = "http://mastodon.example.org/users/admin" user = insert( :user, local: false, nickname: "admin@mastodon.example.org", ap_id: ap_id ) {:ok, fetched_user} = User.get_or_fetch(ap_id) freshed_user = refresh_record(user) assert freshed_user == fetched_user end test "gets an existing user by nickname starting with http" do user = insert(:user, nickname: "httpssome") {:ok, fetched_user} = User.get_or_fetch("httpssome") assert user == fetched_user end end describe "get_or_fetch/1 remote users with tld, while BE is runned on subdomain" do setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true) test "for mastodon" do ap_id = "a@mastodon.example" {:ok, fetched_user} = User.get_or_fetch(ap_id) assert fetched_user.ap_id == "https://sub.mastodon.example/users/a" assert fetched_user.nickname == "a@mastodon.example" end test "for pleroma" do ap_id = "a@pleroma.example" {:ok, fetched_user} = User.get_or_fetch(ap_id) assert fetched_user.ap_id == "https://sub.pleroma.example/users/a" assert fetched_user.nickname == "a@pleroma.example" end end describe "fetching a user from nickname or trying to build one" do test "gets an existing user" do user = insert(:user) {:ok, fetched_user} = User.get_or_fetch_by_nickname(user.nickname) assert user == fetched_user end test "gets an existing user, case insensitive" do user = insert(:user, nickname: "nick") {:ok, fetched_user} = User.get_or_fetch_by_nickname("NICK") assert user == fetched_user end test "gets an existing user by fully qualified nickname" do user = insert(:user) {:ok, fetched_user} = User.get_or_fetch_by_nickname(user.nickname <> "@" <> Pleroma.Web.Endpoint.host()) assert user == fetched_user end test "gets an existing user by fully qualified nickname, case insensitive" do user = insert(:user, nickname: "nick") casing_altered_fqn = String.upcase(user.nickname <> "@" <> Pleroma.Web.Endpoint.host()) {:ok, fetched_user} = User.get_or_fetch_by_nickname(casing_altered_fqn) assert user == fetched_user end @tag capture_log: true test "returns nil if no user could be fetched" do {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la") assert fetched_user == "not found nonexistant@social.heldscal.la" end test "returns nil for nonexistant local user" do {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant") assert fetched_user == "not found nonexistant" end test "updates an existing user, if stale" do a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800) orig_user = insert( :user, local: false, nickname: "admin@mastodon.example.org", ap_id: "http://mastodon.example.org/users/admin", last_refreshed_at: a_week_ago ) assert orig_user.last_refreshed_at == a_week_ago {:ok, user} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin") assert user.inbox refute user.last_refreshed_at == orig_user.last_refreshed_at end test "if nicknames clash, the old user gets a prefix with the old id to the nickname" do a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800) orig_user = insert( :user, local: false, nickname: "admin@mastodon.example.org", ap_id: "http://mastodon.example.org/users/harinezumigari", last_refreshed_at: a_week_ago ) assert orig_user.last_refreshed_at == a_week_ago {:ok, user} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin") assert user.inbox refute user.id == orig_user.id orig_user = User.get_by_id(orig_user.id) assert orig_user.nickname == "#{orig_user.id}.admin@mastodon.example.org" end @tag capture_log: true test "it returns the old user if stale, but unfetchable" do a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800) orig_user = insert( :user, local: false, nickname: "admin@mastodon.example.org", ap_id: "http://mastodon.example.org/users/raymoo", last_refreshed_at: a_week_ago ) assert orig_user.last_refreshed_at == a_week_ago {:ok, user} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/raymoo") assert user.last_refreshed_at == orig_user.last_refreshed_at end test "it doesn't fail on invalid alsoKnownAs entries" do Tesla.Mock.mock(fn %{url: "https://mbp.example.com/"} -> %Tesla.Env{ status: 200, body: "test/fixtures/microblogpub/user_with_invalid_also_known_as.json" |> File.read!(), headers: [{"content-type", "application/activity+json"}] } _ -> %Tesla.Env{status: 404} end) assert {:ok, %User{also_known_as: []}} = User.get_or_fetch_by_ap_id("https://mbp.example.com/") end end test "returns an ap_id for a user" do user = insert(:user) assert User.ap_id(user) == url(@endpoint, ~p[/users/#{user.nickname}]) end test "returns an ap_followers link for a user" do user = insert(:user) assert User.ap_followers(user) == url(@endpoint, ~p[/users/#{user.nickname}/followers]) end describe "remote user changeset" do @valid_remote %{ bio: "hello", name: "Someone", nickname: "a@b.de", ap_id: "http...", avatar: %{some: "avatar"} } setup do: clear_config([:instance, :user_bio_length]) setup do: clear_config([:instance, :user_name_length]) test "it confirms validity" do cs = User.remote_user_changeset(@valid_remote) assert cs.valid? end test "it sets the follower_adress" do cs = User.remote_user_changeset(@valid_remote) # remote users get a fake local follower address assert cs.changes.follower_address == User.ap_followers(%User{nickname: @valid_remote[:nickname]}) end test "it enforces the fqn format for nicknames" do cs = User.remote_user_changeset(%{@valid_remote | nickname: "bla"}) assert Ecto.Changeset.get_field(cs, :local) == false assert cs.changes.avatar refute cs.valid? end test "it has required fields" do [:ap_id] |> Enum.each(fn field -> cs = User.remote_user_changeset(Map.delete(@valid_remote, field)) refute cs.valid? end) end test "it is invalid given a local user" do user = insert(:user) cs = User.remote_user_changeset(user, %{name: "tom from myspace"}) refute cs.valid? end end describe "followers and friends" do test "gets all followers for a given user" do user = insert(:user) follower_one = insert(:user) follower_two = insert(:user) not_follower = insert(:user) {:ok, follower_one, user} = User.follow(follower_one, user) {:ok, follower_two, user} = User.follow(follower_two, user) res = User.get_followers(user) assert Enum.member?(res, follower_one) assert Enum.member?(res, follower_two) refute Enum.member?(res, not_follower) end test "gets all friends (followed users) for a given user" do user = insert(:user) followed_one = insert(:user) followed_two = insert(:user) not_followed = insert(:user) {:ok, user, followed_one} = User.follow(user, followed_one) {:ok, user, followed_two} = User.follow(user, followed_two) res = User.get_friends(user) followed_one = User.get_cached_by_ap_id(followed_one.ap_id) followed_two = User.get_cached_by_ap_id(followed_two.ap_id) assert Enum.member?(res, followed_one) assert Enum.member?(res, followed_two) refute Enum.member?(res, not_followed) end end describe "updating note and follower count" do test "it sets the note_count property" do note = insert(:note) user = User.get_cached_by_ap_id(note.data["actor"]) assert user.note_count == 0 {:ok, user} = User.update_note_count(user) assert user.note_count == 1 end test "it increases the note_count property" do note = insert(:note) user = User.get_cached_by_ap_id(note.data["actor"]) assert user.note_count == 0 {:ok, user} = User.increase_note_count(user) assert user.note_count == 1 {:ok, user} = User.increase_note_count(user) assert user.note_count == 2 end test "it decreases the note_count property" do note = insert(:note) user = User.get_cached_by_ap_id(note.data["actor"]) assert user.note_count == 0 {:ok, user} = User.increase_note_count(user) assert user.note_count == 1 {:ok, user} = User.decrease_note_count(user) assert user.note_count == 0 {:ok, user} = User.decrease_note_count(user) assert user.note_count == 0 end test "it sets the follower_count property" do user = insert(:user) follower = insert(:user) User.follow(follower, user) assert user.follower_count == 0 {:ok, user} = User.update_follower_count(user) assert user.follower_count == 1 end end describe "mutes" do test "it mutes people" do user = insert(:user) muted_user = insert(:user) refute User.mutes?(user, muted_user) refute User.muted_notifications?(user, muted_user) {:ok, _user_relationships} = User.mute(user, muted_user) assert User.mutes?(user, muted_user) assert User.muted_notifications?(user, muted_user) end test "expiring" do user = insert(:user) muted_user = insert(:user) {:ok, _user_relationships} = User.mute(user, muted_user, %{expires_in: 60}) assert User.mutes?(user, muted_user) worker = Pleroma.Workers.MuteExpireWorker args = %{"op" => "unmute_user", "muter_id" => user.id, "mutee_id" => muted_user.id} assert_enqueued( worker: worker, args: args ) assert :ok = perform_job(worker, args) refute User.mutes?(user, muted_user) refute User.muted_notifications?(user, muted_user) end test "it unmutes users" do user = insert(:user) muted_user = insert(:user) {:ok, _user_relationships} = User.mute(user, muted_user) {:ok, _user_mute} = User.unmute(user, muted_user) refute User.mutes?(user, muted_user) refute User.muted_notifications?(user, muted_user) end test "it unmutes users by id" do user = insert(:user) muted_user = insert(:user) {:ok, _user_relationships} = User.mute(user, muted_user) {:ok, _user_mute} = User.unmute(user.id, muted_user.id) refute User.mutes?(user, muted_user) refute User.muted_notifications?(user, muted_user) end test "it mutes user without notifications" do user = insert(:user) muted_user = insert(:user) refute User.mutes?(user, muted_user) refute User.muted_notifications?(user, muted_user) {:ok, _user_relationships} = User.mute(user, muted_user, %{notifications: false}) assert User.mutes?(user, muted_user) refute User.muted_notifications?(user, muted_user) end end describe "blocks" do test "it blocks people" do user = insert(:user) blocked_user = insert(:user) refute User.blocks?(user, blocked_user) {:ok, _user_relationship} = User.block(user, blocked_user) assert User.blocks?(user, blocked_user) end test "it blocks domains" do user = insert(:user) blocked_user = insert(:user) refute User.blocks_domain?(user, blocked_user) url = URI.parse(blocked_user.ap_id) {:ok, user} = User.block_domain(user, url.host) assert User.blocks_domain?(user, blocked_user) end test "it unblocks users" do user = insert(:user) blocked_user = insert(:user) {:ok, _user_relationship} = User.block(user, blocked_user) {:ok, _user_block} = User.unblock(user, blocked_user) refute User.blocks?(user, blocked_user) end test "it unblocks domains" do user = insert(:user) blocked_user = insert(:user) url = URI.parse(blocked_user.ap_id) {:ok, user} = User.block_domain(user, url.host) {:ok, user} = User.unblock_domain(user, url.host) refute User.blocks_domain?(user, blocked_user) end test "blocks tear down cyclical follow relationships" do blocker = insert(:user) blocked = insert(:user) {:ok, blocker, blocked} = User.follow(blocker, blocked) {:ok, blocked, blocker} = User.follow(blocked, blocker) assert User.following?(blocker, blocked) assert User.following?(blocked, blocker) {:ok, _user_relationship} = User.block(blocker, blocked) blocked = User.get_cached_by_id(blocked.id) assert User.blocks?(blocker, blocked) refute User.following?(blocker, blocked) refute User.following?(blocked, blocker) end test "blocks tear down blocker->blocked follow relationships" do blocker = insert(:user) blocked = insert(:user) {:ok, blocker, blocked} = User.follow(blocker, blocked) assert User.following?(blocker, blocked) refute User.following?(blocked, blocker) {:ok, _user_relationship} = User.block(blocker, blocked) blocked = User.get_cached_by_id(blocked.id) assert User.blocks?(blocker, blocked) refute User.following?(blocker, blocked) refute User.following?(blocked, blocker) end test "blocks tear down blocked->blocker follow relationships" do blocker = insert(:user) blocked = insert(:user) {:ok, blocked, blocker} = User.follow(blocked, blocker) refute User.following?(blocker, blocked) assert User.following?(blocked, blocker) {:ok, _user_relationship} = User.block(blocker, blocked) blocked = User.get_cached_by_id(blocked.id) assert User.blocks?(blocker, blocked) refute User.following?(blocker, blocked) refute User.following?(blocked, blocker) end test "blocks tear down blocked->blocker subscription relationships" do blocker = insert(:user) blocked = insert(:user) {:ok, _subscription} = User.subscribe(blocked, blocker) assert User.subscribed_to?(blocked, blocker) refute User.subscribed_to?(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked) assert User.blocks?(blocker, blocked) refute User.subscribed_to?(blocker, blocked) refute User.subscribed_to?(blocked, blocker) end end describe "domain blocking" do test "blocks domains" do user = insert(:user) collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com") assert User.blocks?(user, collateral_user) end test "does not block domain with same end" do user = insert(:user) collateral_user = insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"}) {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com") refute User.blocks?(user, collateral_user) end test "does not block domain with same end if wildcard added" do user = insert(:user) collateral_user = insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"}) {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com") refute User.blocks?(user, collateral_user) end # This behaviour is not honoured by the timeline query # re-add at a later date when UX is established # test "blocks domain with wildcard for subdomain" do # user = insert(:user) # # user_from_subdomain = # insert(:user, %{ap_id: "https://subdomain.awful-and-rude-instance.com/user/bully"}) # # user_with_two_subdomains = # insert(:user, %{ # ap_id: "https://subdomain.second_subdomain.awful-and-rude-instance.com/user/bully" # }) # # user_domain = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) # # {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com") # # assert User.blocks?(user, user_from_subdomain) # assert User.blocks?(user, user_with_two_subdomains) # assert User.blocks?(user, user_domain) # end test "unblocks domains" do user = insert(:user) collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com") {:ok, user} = User.unblock_domain(user, "awful-and-rude-instance.com") refute User.blocks?(user, collateral_user) end test "follows take precedence over domain blocks" do user = insert(:user) good_eggo = insert(:user, %{ap_id: "https://meanies.social/user/cuteposter"}) {:ok, user} = User.block_domain(user, "meanies.social") {:ok, user, good_eggo} = User.follow(user, good_eggo) refute User.blocks?(user, good_eggo) end end describe "get_recipients_from_activity" do test "works for announces" do actor = insert(:user) user = insert(:user, local: true) {:ok, activity} = CommonAPI.post(actor, %{status: "hello"}) {:ok, announce} = CommonAPI.repeat(activity.id, user) recipients = User.get_recipients_from_activity(announce) assert user in recipients end test "get recipients" do actor = insert(:user) user = insert(:user, local: true) user_two = insert(:user, local: false) addressed = insert(:user, local: true) addressed_remote = insert(:user, local: false) {:ok, activity} = CommonAPI.post(actor, %{ status: "hey @#{addressed.nickname} @#{addressed_remote.nickname}" }) assert Enum.map([actor, addressed], & &1.ap_id) -- Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == [] {:ok, user, actor} = User.follow(user, actor) {:ok, _user_two, _actor} = User.follow(user_two, actor) recipients = User.get_recipients_from_activity(activity) assert length(recipients) == 3 assert user in recipients assert addressed in recipients end test "has following" do actor = insert(:user) user = insert(:user) user_two = insert(:user) addressed = insert(:user, local: true) {:ok, activity} = CommonAPI.post(actor, %{ status: "hey @#{addressed.nickname}" }) assert Enum.map([actor, addressed], & &1.ap_id) -- Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == [] {:ok, _actor, _user} = User.follow(actor, user) {:ok, _actor, _user_two} = User.follow(actor, user_two) recipients = User.get_recipients_from_activity(activity) assert length(recipients) == 2 assert addressed in recipients end end describe ".set_activation" do test "can de-activate then re-activate a user" do user = insert(:user) assert user.is_active {:ok, user} = User.set_activation(user, false) refute user.is_active {:ok, user} = User.set_activation(user, true) assert user.is_active end test "hide a user from followers" do user = insert(:user) user2 = insert(:user) {:ok, user, user2} = User.follow(user, user2) {:ok, _user} = User.set_activation(user, false) user2 = User.get_cached_by_id(user2.id) assert user2.follower_count == 0 assert [] = User.get_followers(user2) end test "hide a user from friends" do user = insert(:user) user2 = insert(:user) {:ok, user2, user} = User.follow(user2, user) assert user2.following_count == 1 assert User.following_count(user2) == 1 {:ok, _user} = User.set_activation(user, false) user2 = User.get_cached_by_id(user2.id) assert refresh_record(user2).following_count == 0 assert user2.following_count == 0 assert User.following_count(user2) == 0 assert [] = User.get_friends(user2) end test "hide a user's statuses from timelines and notifications" do user = insert(:user) user2 = insert(:user) {:ok, user2, user} = User.follow(user2, user) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{user2.nickname}"}) activity = Repo.preload(activity, :bookmark) [notification] = Pleroma.Notification.for_user(user2) assert notification.activity.id == activity.id assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark) assert [%{activity | thread_muted?: CommonAPI.thread_muted?(user2, activity)}] == ActivityPub.fetch_activities([user2.ap_id | User.following(user2)], %{ user: user2 }) {:ok, _user} = User.set_activation(user, false) assert [] == ActivityPub.fetch_public_activities(%{}) assert [] == Pleroma.Notification.for_user(user2) assert [] == ActivityPub.fetch_activities([user2.ap_id | User.following(user2)], %{ user: user2 }) end end describe "approve" do test "approves a user" do user = insert(:user, is_approved: false) refute user.is_approved {:ok, user} = User.approve(user) assert user.is_approved end test "approves a list of users" do unapproved_users = [ insert(:user, is_approved: false), insert(:user, is_approved: false), insert(:user, is_approved: false) ] {:ok, users} = User.approve(unapproved_users) assert Enum.count(users) == 3 Enum.each(users, fn user -> assert user.is_approved end) end test "it sends welcome email if it is set" do clear_config([:welcome, :email, :enabled], true) clear_config([:welcome, :email, :sender], "tester@test.me") user = insert(:user, is_approved: false) welcome_user = insert(:user, email: "tester@test.me") instance_name = Pleroma.Config.get([:instance, :name]) User.approve(user) ObanHelpers.perform_all() assert_email_sent( from: {instance_name, welcome_user.email}, to: {user.name, user.email}, html_body: "Welcome to #{instance_name}" ) end test "approving an approved user does not trigger post-register actions" do clear_config([:welcome, :email, :enabled], true) user = insert(:user, is_approved: true) User.approve(user) ObanHelpers.perform_all() assert_no_email_sent() end end describe "confirm" do test "confirms a user" do user = insert(:user, is_confirmed: false) refute user.is_confirmed {:ok, user} = User.confirm(user) assert user.is_confirmed end test "confirms a list of users" do unconfirmed_users = [ insert(:user, is_confirmed: false), insert(:user, is_confirmed: false), insert(:user, is_confirmed: false) ] {:ok, users} = User.confirm(unconfirmed_users) assert Enum.count(users) == 3 Enum.each(users, fn user -> assert user.is_confirmed end) end test "sends approval emails when `is_approved: false`" do admin = insert(:user, is_admin: true) user = insert(:user, is_confirmed: false, is_approved: false) User.confirm(user) ObanHelpers.perform_all() user_email = Pleroma.Emails.UserEmail.approval_pending_email(user) admin_email = Pleroma.Emails.AdminEmail.new_unapproved_registration(admin, user) notify_email = Pleroma.Config.get([:instance, :notify_email]) instance_name = Pleroma.Config.get([:instance, :name]) # User approval email assert_email_sent( from: {instance_name, notify_email}, to: {user.name, user.email}, html_body: user_email.html_body ) # Admin email assert_email_sent( from: {instance_name, notify_email}, to: {admin.name, admin.email}, html_body: admin_email.html_body ) end test "confirming a confirmed user does not trigger post-register actions" do user = insert(:user, is_confirmed: true, is_approved: false) User.confirm(user) ObanHelpers.perform_all() assert_no_email_sent() end end describe "delete" do setup do {:ok, user} = insert(:user) |> User.set_cache() [user: user] end setup do: clear_config([:instance, :federating]) test ".delete_user_activities deletes all create activities", %{user: user} do {:ok, activity} = CommonAPI.post(user, %{status: "2hu"}) User.delete_user_activities(user) # TODO: Test removal favorites, repeats, delete activities. refute Activity.get_by_id(activity.id) end test "it deactivates a user, all follow relationships and all activities", %{user: user} do follower = insert(:user) {:ok, follower, user} = User.follow(follower, user) locked_user = insert(:user, name: "locked", is_locked: true) {:ok, _, _} = User.follow(user, locked_user, :follow_pending) object = insert(:note, user: user) activity = insert(:note_activity, user: user, note: object) object_two = insert(:note, user: follower) activity_two = insert(:note_activity, user: follower, note: object_two) {:ok, like} = CommonAPI.favorite(user, activity_two.id) {:ok, like_two} = CommonAPI.favorite(follower, activity.id) {:ok, repeat} = CommonAPI.repeat(activity_two.id, user) {:ok, job} = User.delete(user) {:ok, _user} = ObanHelpers.perform(job) follower = User.get_cached_by_id(follower.id) refute User.following?(follower, user) assert %{is_active: false} = User.get_by_id(user.id) assert [] == User.get_follow_requests(locked_user) user_activities = user.ap_id |> Activity.Queries.by_actor() |> Repo.all() |> Enum.map(fn act -> act.data["type"] end) assert Enum.all?(user_activities, fn act -> act in ~w(Delete Undo) end) refute Activity.get_by_id(activity.id) refute Activity.get_by_id(like.id) refute Activity.get_by_id(like_two.id) refute Activity.get_by_id(repeat.id) end end test "delete/1 when confirmation is pending deletes the user" do clear_config([:instance, :account_activation_required], true) user = insert(:user, is_confirmed: false) {:ok, job} = User.delete(user) {:ok, _} = ObanHelpers.perform(job) refute User.get_cached_by_id(user.id) refute User.get_by_id(user.id) end test "delete/1 when approval is pending deletes the user" do user = insert(:user, is_approved: false) {:ok, job} = User.delete(user) {:ok, _} = ObanHelpers.perform(job) refute User.get_cached_by_id(user.id) refute User.get_by_id(user.id) end test "delete/1 purges a user when they wouldn't be fully deleted" do user = insert(:user, %{ bio: "eyy lmao", name: "qqqqqqq", password_hash: "pdfk2$1b3n159001", keys: "RSA begin buplic key", public_key: "--PRIVATE KEYE--", avatar: %{"a" => "b"}, tags: ["qqqqq"], banner: %{"a" => "b"}, background: %{"a" => "b"}, note_count: 9, follower_count: 9, following_count: 9001, is_locked: true, is_confirmed: true, password_reset_pending: true, is_approved: true, registration_reason: "ahhhhh", confirmation_token: "qqqq", domain_blocks: ["lain.com"], is_active: false, ap_enabled: true, is_moderator: true, is_admin: true, mastofe_settings: %{"a" => "b"}, mascot: %{"a" => "b"}, emoji: %{"a" => "b"}, pleroma_settings_store: %{"q" => "x"}, fields: [%{"gg" => "qq"}], raw_fields: [%{"gg" => "qq"}], is_discoverable: true, also_known_as: ["https://lol.olo/users/loll"] }) {:ok, job} = User.delete(user) {:ok, _} = ObanHelpers.perform(job) user = User.get_by_id(user.id) assert %User{ bio: "", raw_bio: nil, email: nil, name: nil, password_hash: nil, keys: "RSA begin buplic key", public_key: "--PRIVATE KEYE--", avatar: %{}, tags: [], last_refreshed_at: nil, last_digest_emailed_at: nil, banner: %{}, background: %{}, note_count: 0, follower_count: 0, following_count: 0, is_locked: false, is_confirmed: true, password_reset_pending: false, is_approved: true, registration_reason: nil, confirmation_token: nil, domain_blocks: [], is_active: false, ap_enabled: false, is_moderator: false, is_admin: false, mastofe_settings: nil, mascot: nil, emoji: %{}, pleroma_settings_store: %{}, fields: [], raw_fields: [], is_discoverable: false, also_known_as: [] } = user end test "delete/1 purges a remote user" do user = insert(:user, %{ name: "qqqqqqq", avatar: %{"a" => "b"}, banner: %{"a" => "b"}, local: false }) {:ok, job} = User.delete(user) {:ok, _} = ObanHelpers.perform(job) user = User.get_by_id(user.id) assert user.name == nil assert user.avatar == %{} assert user.banner == %{} end describe "set_suggestion" do test "suggests a user" do user = insert(:user, is_suggested: false) refute user.is_suggested {:ok, user} = User.set_suggestion(user, true) assert user.is_suggested end test "suggests a list of users" do unsuggested_users = [ insert(:user, is_suggested: false), insert(:user, is_suggested: false), insert(:user, is_suggested: false) ] {:ok, users} = User.set_suggestion(unsuggested_users, true) assert Enum.count(users) == 3 Enum.each(users, fn user -> assert user.is_suggested end) end test "unsuggests a user" do user = insert(:user, is_suggested: true) assert user.is_suggested {:ok, user} = User.set_suggestion(user, false) refute user.is_suggested end end test "get_public_key_for_ap_id fetches a user that's not in the db" do assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin") end describe "per-user rich-text filtering" do test "html_filter_policy returns default policies, when rich-text is enabled" do user = insert(:user) assert Pleroma.Config.get([:markup, :scrub_policy]) == User.html_filter_policy(user) end test "html_filter_policy returns TwitterText scrubber when rich-text is disabled" do user = insert(:user, no_rich_text: true) assert Pleroma.HTML.Scrubber.TwitterText == User.html_filter_policy(user) end end describe "caching" do test "invalidate_cache works" do user = insert(:user) User.set_cache(user) User.invalidate_cache(user) {:ok, nil} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}") {:ok, nil} = Cachex.get(:user_cache, "nickname:#{user.nickname}") end test "User.delete() plugs any possible zombie objects" do user = insert(:user) {:ok, job} = User.delete(user) {:ok, _} = ObanHelpers.perform(job) {:ok, cached_user} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}") assert cached_user != user {:ok, cached_user} = Cachex.get(:user_cache, "nickname:#{user.ap_id}") assert cached_user != user end end describe "account_status/1" do setup do: clear_config([:instance, :account_activation_required]) test "return confirmation_pending for unconfirm user" do clear_config([:instance, :account_activation_required], true) user = insert(:user, is_confirmed: false) assert User.account_status(user) == :confirmation_pending end test "return active for confirmed user" do clear_config([:instance, :account_activation_required], true) user = insert(:user, is_confirmed: true) assert User.account_status(user) == :active end test "return active for remote user" do user = insert(:user, local: false) assert User.account_status(user) == :active end test "returns :password_reset_pending for user with reset password" do user = insert(:user, password_reset_pending: true) assert User.account_status(user) == :password_reset_pending end test "returns :deactivated for deactivated user" do user = insert(:user, local: true, is_confirmed: true, is_active: false) assert User.account_status(user) == :deactivated end test "returns :approval_pending for unapproved user" do user = insert(:user, local: true, is_approved: false) assert User.account_status(user) == :approval_pending user = insert(:user, local: true, is_confirmed: false, is_approved: false) assert User.account_status(user) == :approval_pending end end describe "superuser?/1" do test "returns false for unprivileged users" do user = insert(:user, local: true) refute User.superuser?(user) end test "returns false for remote users" do user = insert(:user, local: false) remote_admin_user = insert(:user, local: false, is_admin: true) refute User.superuser?(user) refute User.superuser?(remote_admin_user) end test "returns true for local moderators" do user = insert(:user, local: true, is_moderator: true) assert User.superuser?(user) end test "returns true for local admins" do user = insert(:user, local: true, is_admin: true) assert User.superuser?(user) end end describe "invisible?/1" do test "returns true for an invisible user" do user = insert(:user, local: true, invisible: true) assert User.invisible?(user) end test "returns false for a non-invisible user" do user = insert(:user, local: true) refute User.invisible?(user) end end describe "visible_for/2" do test "returns true when the account is itself" do user = insert(:user, local: true) assert User.visible_for(user, user) == :visible end test "returns false when the account is unconfirmed and confirmation is required" do clear_config([:instance, :account_activation_required], true) user = insert(:user, local: true, is_confirmed: false) other_user = insert(:user, local: true) refute User.visible_for(user, other_user) == :visible end test "returns true when the account is unconfirmed and confirmation is required but the account is remote" do clear_config([:instance, :account_activation_required], true) user = insert(:user, local: false, is_confirmed: false) other_user = insert(:user, local: true) assert User.visible_for(user, other_user) == :visible end test "returns true when the account is unconfirmed and being viewed by a privileged account (confirmation required)" do clear_config([:instance, :account_activation_required], true) user = insert(:user, local: true, is_confirmed: false) other_user = insert(:user, local: true, is_admin: true) assert User.visible_for(user, other_user) == :visible end end describe "parse_bio/2" do test "preserves hosts in user links text" do remote_user = insert(:user, local: false, nickname: "nick@domain.com") user = insert(:user) bio = "A.k.a. @nick@domain.com" expected_text = ~s(A.k.a. @nick@domain.com) assert expected_text == User.parse_bio(bio, user) end test "Adds rel=me on linkbacked urls" do user = insert(:user, ap_id: "https://social.example.org/users/lain") bio = "http://example.com/rel_me/null" expected_text = "#{bio}" assert expected_text == User.parse_bio(bio, user) bio = "http://example.com/rel_me/link" expected_text = "#{bio}" assert expected_text == User.parse_bio(bio, user) bio = "http://example.com/rel_me/anchor" expected_text = "#{bio}" assert expected_text == User.parse_bio(bio, user) end end test "follower count is updated when a follower is blocked" do user = insert(:user) follower = insert(:user) follower2 = insert(:user) follower3 = insert(:user) {:ok, follower, user} = User.follow(follower, user) {:ok, _follower2, _user} = User.follow(follower2, user) {:ok, _follower3, _user} = User.follow(follower3, user) {:ok, _user_relationship} = User.block(user, follower) user = refresh_record(user) assert user.follower_count == 2 end describe "list_inactive_users_query/1" do defp days_ago(days) do NaiveDateTime.add( NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second), -days * 60 * 60 * 24, :second ) end test "Users are inactive by default" do total = 10 users = Enum.map(1..total, fn _ -> insert(:user, last_digest_emailed_at: days_ago(20), is_active: true) end) inactive_users_ids = Pleroma.User.list_inactive_users_query() |> Pleroma.Repo.all() |> Enum.map(& &1.id) Enum.each(users, fn user -> assert user.id in inactive_users_ids end) end test "Only includes users who has no recent activity" do total = 10 users = Enum.map(1..total, fn _ -> insert(:user, last_digest_emailed_at: days_ago(20), is_active: true) end) {inactive, active} = Enum.split(users, trunc(total / 2)) Enum.map(active, fn user -> to = Enum.random(users -- [user]) {:ok, _} = CommonAPI.post(user, %{ status: "hey @#{to.nickname}" }) end) inactive_users_ids = Pleroma.User.list_inactive_users_query() |> Pleroma.Repo.all() |> Enum.map(& &1.id) Enum.each(active, fn user -> refute user.id in inactive_users_ids end) Enum.each(inactive, fn user -> assert user.id in inactive_users_ids end) end test "Only includes users with no read notifications" do total = 10 users = Enum.map(1..total, fn _ -> insert(:user, last_digest_emailed_at: days_ago(20), is_active: true) end) [sender | recipients] = users {inactive, active} = Enum.split(recipients, trunc(total / 2)) Enum.each(recipients, fn to -> {:ok, _} = CommonAPI.post(sender, %{ status: "hey @#{to.nickname}" }) {:ok, _} = CommonAPI.post(sender, %{ status: "hey again @#{to.nickname}" }) end) Enum.each(active, fn user -> [n1, _n2] = Pleroma.Notification.for_user(user) {:ok, _} = Pleroma.Notification.read_one(user, n1.id) end) inactive_users_ids = Pleroma.User.list_inactive_users_query() |> Pleroma.Repo.all() |> Enum.map(& &1.id) Enum.each(active, fn user -> refute user.id in inactive_users_ids end) Enum.each(inactive, fn user -> assert user.id in inactive_users_ids end) end end describe "get_ap_ids_by_nicknames" do test "it returns a list of AP ids for a given set of nicknames" do user = insert(:user) user_two = insert(:user) ap_ids = User.get_ap_ids_by_nicknames([user.nickname, user_two.nickname, "nonexistent"]) assert length(ap_ids) == 2 assert user.ap_id in ap_ids assert user_two.ap_id in ap_ids end end describe "sync followers count" do setup do user1 = insert(:user, local: false, ap_id: "http://remote.org/users/masto_closed") user2 = insert(:user, local: false, ap_id: "http://remote.org/users/fuser2") insert(:user, local: true) insert(:user, local: false, is_active: false) {:ok, user1: user1, user2: user2} end test "external_users/1 external active users with limit", %{user1: user1, user2: user2} do [fdb_user1] = User.external_users(limit: 1) assert fdb_user1.ap_id assert fdb_user1.ap_id == user1.ap_id assert fdb_user1.id == user1.id [fdb_user2] = User.external_users(max_id: fdb_user1.id, limit: 1) assert fdb_user2.ap_id assert fdb_user2.ap_id == user2.ap_id assert fdb_user2.id == user2.id assert User.external_users(max_id: fdb_user2.id, limit: 1) == [] end end describe "is_internal_user?/1" do test "non-internal user returns false" do user = insert(:user) refute User.is_internal_user?(user) end test "user with no nickname returns true" do user = insert(:user, %{nickname: nil}) assert User.is_internal_user?(user) end test "user with internal-prefixed nickname returns true" do user = insert(:user, %{nickname: "internal.test"}) assert User.is_internal_user?(user) end end describe "update_and_set_cache/1" do test "returns error when user is stale instead Ecto.StaleEntryError" do user = insert(:user) changeset = Ecto.Changeset.change(user, bio: "test") Repo.delete(user) assert {:error, %Ecto.Changeset{errors: [id: {"is stale", [stale: true]}], valid?: false}} = User.update_and_set_cache(changeset) end test "performs update cache if user updated" do user = insert(:user) assert {:ok, nil} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}") changeset = Ecto.Changeset.change(user, bio: "test-bio") assert {:ok, %User{bio: "test-bio"} = user} = User.update_and_set_cache(changeset) assert {:ok, user} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}") assert %User{bio: "test-bio"} = User.get_cached_by_ap_id(user.ap_id) end test "removes report notifs when user isn't superuser any more" do report_activity = insert(:report_activity) user = insert(:user, is_moderator: true, is_admin: true) {:ok, _} = Notification.create_notifications(report_activity) assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user) {:ok, user} = user |> User.admin_api_update(%{is_moderator: false}) # is still superuser because still admin assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user) {:ok, user} = user |> User.admin_api_update(%{is_moderator: true, is_admin: false}) # is still superuser because still moderator assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user) {:ok, user} = user |> User.admin_api_update(%{is_moderator: false}) # is not a superuser any more assert [] = Notification.for_user(user) end end describe "following/followers synchronization" do setup do: clear_config([:instance, :external_user_synchronization]) test "updates the counters normally on following/getting a follow when disabled" do clear_config([:instance, :external_user_synchronization], false) user = insert(:user) other_user = insert(:user, local: false, follower_address: "http://remote.org/users/masto_closed/followers", following_address: "http://remote.org/users/masto_closed/following", ap_enabled: true ) assert other_user.following_count == 0 assert other_user.follower_count == 0 {:ok, user, other_user} = Pleroma.User.follow(user, other_user) assert user.following_count == 1 assert other_user.follower_count == 1 end test "synchronizes the counters with the remote instance for the followed when enabled" do clear_config([:instance, :external_user_synchronization], false) user = insert(:user) other_user = insert(:user, local: false, follower_address: "http://remote.org/users/masto_closed/followers", following_address: "http://remote.org/users/masto_closed/following", ap_enabled: true ) assert other_user.following_count == 0 assert other_user.follower_count == 0 clear_config([:instance, :external_user_synchronization], true) {:ok, _user, other_user} = User.follow(user, other_user) assert other_user.follower_count == 437 end test "synchronizes the counters with the remote instance for the follower when enabled" do clear_config([:instance, :external_user_synchronization], false) user = insert(:user) other_user = insert(:user, local: false, follower_address: "http://remote.org/users/masto_closed/followers", following_address: "http://remote.org/users/masto_closed/following", ap_enabled: true ) assert other_user.following_count == 0 assert other_user.follower_count == 0 clear_config([:instance, :external_user_synchronization], true) {:ok, other_user, _user} = User.follow(other_user, user) assert other_user.following_count == 152 end end describe "change_email/2" do setup do [user: insert(:user)] end test "blank email returns error if we require an email on registration", %{user: user} do orig_account_activation_required = Pleroma.Config.get([:instance, :account_activation_required]) Pleroma.Config.put([:instance, :account_activation_required], true) on_exit(fn -> Pleroma.Config.put( [:instance, :account_activation_required], orig_account_activation_required ) end) assert {:error, %{errors: [email: {"can't be blank", _}]}} = User.change_email(user, "") assert {:error, %{errors: [email: {"can't be blank", _}]}} = User.change_email(user, nil) end test "blank email should be fine if we do not require an email on registration", %{user: user} do orig_account_activation_required = Pleroma.Config.get([:instance, :account_activation_required]) Pleroma.Config.put([:instance, :account_activation_required], false) on_exit(fn -> Pleroma.Config.put( [:instance, :account_activation_required], orig_account_activation_required ) end) assert {:ok, %User{email: nil}} = User.change_email(user, "") assert {:ok, %User{email: nil}} = User.change_email(user, nil) end test "non unique email returns error", %{user: user} do %{email: email} = insert(:user) assert {:error, %{errors: [email: {"has already been taken", _}]}} = User.change_email(user, email) end test "invalid email returns error", %{user: user} do assert {:error, %{errors: [email: {"has invalid format", _}]}} = User.change_email(user, "cofe") end test "changes email", %{user: user} do assert {:ok, %User{email: "cofe@cofe.party"}} = User.change_email(user, "cofe@cofe.party") end test "adds email", %{user: user} do orig_account_activation_required = Pleroma.Config.get([:instance, :account_activation_required]) Pleroma.Config.put([:instance, :account_activation_required], false) on_exit(fn -> Pleroma.Config.put( [:instance, :account_activation_required], orig_account_activation_required ) end) assert {:ok, _} = User.change_email(user, "") Pleroma.Config.put([:instance, :account_activation_required], true) assert {:ok, %User{email: "cofe2@cofe.party"}} = User.change_email(user, "cofe2@cofe.party") end end describe "get_cached_by_nickname_or_id" do setup do local_user = insert(:user) remote_user = insert(:user, nickname: "nickname@example.com", local: false) [local_user: local_user, remote_user: remote_user] end setup do: clear_config([:instance, :limit_to_local_content]) test "allows getting remote users by id no matter what :limit_to_local_content is set to", %{ remote_user: remote_user } do clear_config([:instance, :limit_to_local_content], false) assert %User{} = User.get_cached_by_nickname_or_id(remote_user.id) clear_config([:instance, :limit_to_local_content], true) assert %User{} = User.get_cached_by_nickname_or_id(remote_user.id) clear_config([:instance, :limit_to_local_content], :unauthenticated) assert %User{} = User.get_cached_by_nickname_or_id(remote_user.id) end test "disallows getting remote users by nickname without authentication when :limit_to_local_content is set to :unauthenticated", %{remote_user: remote_user} do clear_config([:instance, :limit_to_local_content], :unauthenticated) assert nil == User.get_cached_by_nickname_or_id(remote_user.nickname) end test "allows getting remote users by nickname with authentication when :limit_to_local_content is set to :unauthenticated", %{remote_user: remote_user, local_user: local_user} do clear_config([:instance, :limit_to_local_content], :unauthenticated) assert %User{} = User.get_cached_by_nickname_or_id(remote_user.nickname, for: local_user) end test "disallows getting remote users by nickname when :limit_to_local_content is set to true", %{remote_user: remote_user} do clear_config([:instance, :limit_to_local_content], true) assert nil == User.get_cached_by_nickname_or_id(remote_user.nickname) end test "allows getting local users by nickname no matter what :limit_to_local_content is set to", %{local_user: local_user} do clear_config([:instance, :limit_to_local_content], false) assert %User{} = User.get_cached_by_nickname_or_id(local_user.nickname) clear_config([:instance, :limit_to_local_content], true) assert %User{} = User.get_cached_by_nickname_or_id(local_user.nickname) clear_config([:instance, :limit_to_local_content], :unauthenticated) assert %User{} = User.get_cached_by_nickname_or_id(local_user.nickname) end end describe "update_email_notifications/2" do setup do user = insert(:user, email_notifications: %{"digest" => true}) {:ok, user: user} end test "Notifications are updated", %{user: user} do true = user.email_notifications["digest"] assert {:ok, result} = User.update_email_notifications(user, %{"digest" => false}) assert result.email_notifications["digest"] == false end end describe "local_nickname/1" do test "returns nickname without host" do assert User.local_nickname("@mentioned") == "mentioned" assert User.local_nickname("a_local_nickname") == "a_local_nickname" assert User.local_nickname("nickname@host.com") == "nickname" end end describe "full_nickname/1" do test "returns fully qualified nickname for local and remote users" do local_user = insert(:user, nickname: "local_user", ap_id: "https://somehost.com/users/local_user") remote_user = insert(:user, nickname: "remote@host.com", local: false) assert User.full_nickname(local_user) == "local_user@somehost.com" assert User.full_nickname(remote_user) == "remote@host.com" end test "strips leading @ from mentions" do assert User.full_nickname("@mentioned") == "mentioned" assert User.full_nickname("@nickname@host.com") == "nickname@host.com" end test "does not modify nicknames" do assert User.full_nickname("nickname") == "nickname" assert User.full_nickname("nickname@host.com") == "nickname@host.com" end end test "avatar fallback" do user = insert(:user) assert User.avatar_url(user) =~ "/images/avi.png" clear_config([:assets, :default_user_avatar], "avatar.png") user = User.get_cached_by_nickname_or_id(user.nickname) assert User.avatar_url(user) =~ "avatar.png" assert User.avatar_url(user, no_default: true) == nil end test "avatar object with nil in href" do user = insert(:user, avatar: %{"url" => [%{"href" => nil}]}) assert User.avatar_url(user) != nil end test "banner object with nil in href" do user = insert(:user, banner: %{"url" => [%{"href" => nil}]}) assert User.banner_url(user) != nil end test "get_host/1" do user = insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain") assert User.get_host(user) == "lain.com" end test "update_last_active_at/1" do user = insert(:user) assert is_nil(user.last_active_at) test_started_at = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) assert {:ok, user} = User.update_last_active_at(user) assert user.last_active_at >= test_started_at assert user.last_active_at <= NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second) last_active_at = NaiveDateTime.utc_now() |> NaiveDateTime.add(-:timer.hours(24), :millisecond) |> NaiveDateTime.truncate(:second) assert {:ok, user} = user |> cast(%{last_active_at: last_active_at}, [:last_active_at]) |> User.update_and_set_cache() assert user.last_active_at == last_active_at assert {:ok, user} = User.update_last_active_at(user) assert user.last_active_at >= test_started_at assert user.last_active_at <= NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second) end test "active_user_count/1" do insert(:user) insert(:user, %{local: false}) insert(:user, %{last_active_at: NaiveDateTime.utc_now()}) insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), days: -15)}) insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), weeks: -6)}) insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), months: -7)}) insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), years: -2)}) assert User.active_user_count() == 2 assert User.active_user_count(180) == 3 assert User.active_user_count(365) == 4 assert User.active_user_count(1000) == 5 end describe "pins" do setup do user = insert(:user) [user: user, object_id: object_id_from_created_activity(user)] end test "unique pins", %{user: user, object_id: object_id} do assert {:ok, %{pinned_objects: %{^object_id => pinned_at1} = pins} = updated_user} = User.add_pinned_object_id(user, object_id) assert Enum.count(pins) == 1 assert {:ok, %{pinned_objects: %{^object_id => pinned_at2} = pins}} = User.add_pinned_object_id(updated_user, object_id) assert pinned_at1 == pinned_at2 assert Enum.count(pins) == 1 end test "respects max_pinned_statuses limit", %{user: user, object_id: object_id} do clear_config([:instance, :max_pinned_statuses], 1) {:ok, updated} = User.add_pinned_object_id(user, object_id) object_id2 = object_id_from_created_activity(user) {:error, %{errors: errors}} = User.add_pinned_object_id(updated, object_id2) assert Keyword.has_key?(errors, :pinned_objects) end test "remove_pinned_object_id/2", %{user: user, object_id: object_id} do assert {:ok, updated} = User.add_pinned_object_id(user, object_id) {:ok, after_remove} = User.remove_pinned_object_id(updated, object_id) assert after_remove.pinned_objects == %{} end end defp object_id_from_created_activity(user) do %{id: id} = insert(:note_activity, user: user) %{object: %{data: %{"id" => object_id}}} = Activity.get_by_id_with_object(id) object_id end describe "add_alias/2" do test "should add alias for another user" do user = insert(:user) user2 = insert(:user) assert {:ok, user_updated} = user |> User.add_alias(user2) assert user_updated.also_known_as |> length() == 1 assert user2.ap_id in user_updated.also_known_as end test "should add multiple aliases" do user = insert(:user) user2 = insert(:user) user3 = insert(:user) assert {:ok, user} = user |> User.add_alias(user2) assert {:ok, user_updated} = user |> User.add_alias(user3) assert user_updated.also_known_as |> length() == 2 assert user2.ap_id in user_updated.also_known_as assert user3.ap_id in user_updated.also_known_as end test "should not add duplicate aliases" do user = insert(:user) user2 = insert(:user) assert {:ok, user} = user |> User.add_alias(user2) assert {:ok, user_updated} = user |> User.add_alias(user2) assert user_updated.also_known_as |> length() == 1 assert user2.ap_id in user_updated.also_known_as end end describe "alias_users/1" do test "should get aliases for a user" do user = insert(:user) user2 = insert(:user, also_known_as: [user.ap_id]) aliases = user2 |> User.alias_users() assert aliases |> length() == 1 alias_user = aliases |> Enum.at(0) assert alias_user.ap_id == user.ap_id end end describe "delete_alias/2" do test "should delete existing alias" do user = insert(:user) user2 = insert(:user, also_known_as: [user.ap_id]) assert {:ok, user_updated} = user2 |> User.delete_alias(user) assert user_updated.also_known_as == [] end test "should report error on non-existing alias" do user = insert(:user) user2 = insert(:user) user3 = insert(:user, also_known_as: [user.ap_id]) assert {:error, :no_such_alias} = user3 |> User.delete_alias(user2) user3_updated = User.get_cached_by_ap_id(user3.ap_id) assert user3_updated.also_known_as |> length() == 1 assert user.ap_id in user3_updated.also_known_as end end describe "follow_hashtag/2" do test "should follow a hashtag" do user = insert(:user) hashtag = insert(:hashtag) assert {:ok, _} = user |> User.follow_hashtag(hashtag) user = User.get_cached_by_ap_id(user.ap_id) assert user.followed_hashtags |> Enum.count() == 1 assert hashtag.name in Enum.map(user.followed_hashtags, fn %{name: name} -> name end) end test "should not follow a hashtag twice" do user = insert(:user) hashtag = insert(:hashtag) assert {:ok, _} = user |> User.follow_hashtag(hashtag) assert {:ok, _} = user |> User.follow_hashtag(hashtag) user = User.get_cached_by_ap_id(user.ap_id) assert user.followed_hashtags |> Enum.count() == 1 assert hashtag.name in Enum.map(user.followed_hashtags, fn %{name: name} -> name end) end test "can follow multiple hashtags" do user = insert(:user) hashtag = insert(:hashtag) other_hashtag = insert(:hashtag) assert {:ok, _} = user |> User.follow_hashtag(hashtag) assert {:ok, _} = user |> User.follow_hashtag(other_hashtag) user = User.get_cached_by_ap_id(user.ap_id) assert user.followed_hashtags |> Enum.count() == 2 assert hashtag.name in Enum.map(user.followed_hashtags, fn %{name: name} -> name end) assert other_hashtag.name in Enum.map(user.followed_hashtags, fn %{name: name} -> name end) end end describe "unfollow_hashtag/2" do test "should unfollow a hashtag" do user = insert(:user) hashtag = insert(:hashtag) assert {:ok, _} = user |> User.follow_hashtag(hashtag) assert {:ok, _} = user |> User.unfollow_hashtag(hashtag) user = User.get_cached_by_ap_id(user.ap_id) assert user.followed_hashtags |> Enum.count() == 0 end test "should not error when trying to unfollow a hashtag twice" do user = insert(:user) hashtag = insert(:hashtag) assert {:ok, _} = user |> User.follow_hashtag(hashtag) assert {:ok, _} = user |> User.unfollow_hashtag(hashtag) assert {:ok, _} = user |> User.unfollow_hashtag(hashtag) user = User.get_cached_by_ap_id(user.ap_id) assert user.followed_hashtags |> Enum.count() == 0 end end describe "accepts_direct_messages?/2" do test "should return true if the recipient follows the sender and has set accept to :people_i_follow" do recipient = insert(:user, %{ accepts_direct_messages_from: :people_i_follow }) sender = insert(:user) refute User.accepts_direct_messages?(recipient, sender) CommonAPI.follow(recipient, sender) assert User.accepts_direct_messages?(recipient, sender) end test "should return true if the recipient has set accept to :everyone" do recipient = insert(:user, %{accepts_direct_messages_from: :everybody}) sender = insert(:user) assert User.accepts_direct_messages?(recipient, sender) end test "should return false if the receipient set accept to :nobody" do recipient = insert(:user, %{accepts_direct_messages_from: :nobody}) sender = insert(:user) refute User.accepts_direct_messages?(recipient, sender) end end end