use Pleroma.DataCase
import Pleroma.Factory
+ import Mock
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
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, %{info: %{locked: true}})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(accepted_follower, %{"user_id" => locked.id})
- User.maybe_follow(accepted_follower, locked)
+ User.follow(accepted_follower, locked)
assert {:ok, [activity]} = User.get_follow_requests(locked)
assert activity
muted_user = insert(:user)
refute User.mutes?(user, muted_user)
+ refute User.muted_notifications?(user, muted_user)
{:ok, user} = User.mute(user, muted_user)
assert User.mutes?(user, muted_user)
+ assert User.muted_notifications?(user, muted_user)
end
test "it unmutes users" do
{:ok, user} = User.unmute(user, muted_user)
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} = User.mute(user, muted_user, false)
+
+ assert User.mutes?(user, muted_user)
+ refute User.muted_notifications?(user, muted_user)
end
end
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
+
+ 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"})
end
end
- test ".delete_user_activities deletes all create activities" do
- user = insert(:user)
+ describe "delete" do
+ setup do
+ {:ok, user} = insert(:user) |> User.set_cache()
- {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
+ [user: user]
+ end
+
+ test ".delete_user_activities deletes all create activities", %{user: user} do
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
- Ecto.Adapters.SQL.Sandbox.unboxed_run(Repo, fn ->
{:ok, _} = User.delete_user_activities(user)
+
# TODO: Remove favorites, repeats, delete activities.
refute Activity.get_by_id(activity.id)
- end)
- end
+ end
- test ".delete deactivates a user, all follow relationships and all create activities" do
- user = insert(:user)
- followed = insert(:user)
- follower = insert(:user)
+ test "it deletes a user, all follow relationships and all activities", %{user: user} do
+ follower = insert(:user)
+ {:ok, follower} = User.follow(follower, user)
- {:ok, user} = User.follow(user, followed)
- {:ok, follower} = User.follow(follower, user)
+ object = insert(:note, user: user)
+ activity = insert(:note_activity, user: user, note: object)
- {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
- {:ok, activity_two} = CommonAPI.post(follower, %{"status" => "3hu"})
+ object_two = insert(:note, user: follower)
+ activity_two = insert(:note_activity, user: follower, note: object_two)
- {:ok, _, _} = CommonAPI.favorite(activity_two.id, user)
- {:ok, _, _} = CommonAPI.favorite(activity.id, follower)
- {:ok, _, _} = CommonAPI.repeat(activity.id, follower)
+ {:ok, like, _} = CommonAPI.favorite(activity_two.id, user)
+ {:ok, like_two, _} = CommonAPI.favorite(activity.id, follower)
+ {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user)
- {:ok, _} = User.delete(user)
+ {:ok, _} = User.delete(user)
- followed = User.get_cached_by_id(followed.id)
- follower = User.get_cached_by_id(follower.id)
- user = User.get_cached_by_id(user.id)
+ follower = User.get_cached_by_id(follower.id)
- assert user.info.deactivated
+ refute User.following?(follower, user)
+ refute User.get_by_id(user.id)
+ assert {:ok, nil} == Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
- refute User.following?(user, followed)
- refute User.following?(followed, follower)
+ user_activities =
+ user.ap_id
+ |> Activity.query_by_actor()
+ |> Repo.all()
+ |> Enum.map(fn act -> act.data["type"] end)
- # TODO: Remove favorites, repeats, delete activities.
+ 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(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
+
+ test_with_mock "it sends out User Delete activity",
+ %{user: user},
+ Pleroma.Web.ActivityPub.Publisher,
+ [:passthrough],
+ [] do
+ config_path = [:instance, :federating]
+ initial_setting = Pleroma.Config.get(config_path)
+ Pleroma.Config.put(config_path, true)
+
+ {:ok, follower} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
+ {:ok, _} = User.follow(follower, user)
+
+ {:ok, _user} = User.delete(user)
+
+ assert called(
+ Pleroma.Web.ActivityPub.Publisher.publish_one(%{
+ inbox: "http://mastodon.example.org/inbox"
+ })
+ )
+
+ Pleroma.Config.put(config_path, initial_setting)
+ end
end
test "get_public_key_for_ap_id fetches a user that's not in the db" do
end
end
- describe "User.search" do
- test "finds a user by full or partial nickname" do
- user = insert(:user, %{nickname: "john"})
-
- Enum.each(["john", "jo", "j"], fn query ->
- assert user ==
- User.search(query)
- |> List.first()
- |> Map.put(:search_rank, nil)
- |> Map.put(:search_type, nil)
- end)
- end
-
- test "finds a user by full or partial name" do
- user = insert(:user, %{name: "John Doe"})
-
- Enum.each(["John Doe", "JOHN", "doe", "j d", "j", "d"], fn query ->
- assert user ==
- User.search(query)
- |> List.first()
- |> Map.put(:search_rank, nil)
- |> Map.put(:search_type, nil)
- end)
- end
-
- test "finds users, preferring nickname matches over name matches" do
- u1 = insert(:user, %{name: "lain", nickname: "nick1"})
- u2 = insert(:user, %{nickname: "lain", name: "nick1"})
-
- assert [u2.id, u1.id] == Enum.map(User.search("lain"), & &1.id)
- end
-
- test "finds users, considering density of matched tokens" do
- u1 = insert(:user, %{name: "Bar Bar plus Word Word"})
- u2 = insert(:user, %{name: "Word Word Bar Bar Bar"})
-
- assert [u2.id, u1.id] == Enum.map(User.search("bar word"), & &1.id)
- end
-
- test "finds users, ranking by similarity" do
- u1 = insert(:user, %{name: "lain"})
- _u2 = insert(:user, %{name: "ean"})
- u3 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social"})
- u4 = insert(:user, %{nickname: "lain@pleroma.soykaf.com"})
-
- assert [u4.id, u3.id, u1.id] == Enum.map(User.search("lain@ple"), & &1.id)
- end
-
- test "finds users, handling misspelled requests" do
- u1 = insert(:user, %{name: "lain"})
-
- assert [u1.id] == Enum.map(User.search("laiin"), & &1.id)
- end
-
- test "finds users, boosting ranks of friends and followers" do
- u1 = insert(:user)
- u2 = insert(:user, %{name: "Doe"})
- follower = insert(:user, %{name: "Doe"})
- friend = insert(:user, %{name: "Doe"})
-
- {:ok, follower} = User.follow(follower, u1)
- {:ok, u1} = User.follow(u1, friend)
-
- assert [friend.id, follower.id, u2.id] --
- Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []
- end
-
- test "finds a user whose name is nil" do
- _user = insert(:user, %{name: "notamatch", nickname: "testuser@pleroma.amplifie.red"})
- user_two = insert(:user, %{name: nil, nickname: "lain@pleroma.soykaf.com"})
-
- assert user_two ==
- User.search("lain@pleroma.soykaf.com")
- |> List.first()
- |> Map.put(:search_rank, nil)
- |> Map.put(:search_type, nil)
- end
-
- test "does not yield false-positive matches" do
- insert(:user, %{name: "John Doe"})
-
- Enum.each(["mary", "a", ""], fn query ->
- assert [] == User.search(query)
- end)
- end
-
- test "works with URIs" do
- results = User.search("http://mastodon.example.org/users/admin", resolve: true)
- result = results |> List.first()
-
- user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin")
-
- assert length(results) == 1
- assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
- end
- end
-
test "auth_active?/1 works correctly" do
Pleroma.Config.put([:instance, :account_activation_required], true)
assert Map.get(user_show, "followers_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), info: %{deactivated: false})
+ 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), info: %{deactivated: false})
+ end)
+
+ {inactive, active} = Enum.split(users, trunc(total / 2))
+
+ Enum.map(active, fn user ->
+ to = Enum.random(users -- [user])
+
+ {:ok, _} =
+ Pleroma.Web.TwitterAPI.TwitterAPI.create_status(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), info: %{deactivated: false})
+ end)
+
+ [sender | recipients] = users
+ {inactive, active} = Enum.split(recipients, trunc(total / 2))
+
+ Enum.each(recipients, fn to ->
+ {:ok, _} =
+ Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
+ "status" => "hey @#{to.nickname}"
+ })
+
+ {:ok, _} =
+ Pleroma.Web.TwitterAPI.TwitterAPI.create_status(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 "toggle_confirmation/1" do
test "if user is confirmed" do
user = insert(:user, info: %{confirmation_pending: false})
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://localhost:4001/users/masto_closed")
+ user2 = insert(:user, local: false, ap_id: "http://localhost:4001/users/fuser2")
+ insert(:user, local: true)
+ insert(:user, local: false, info: %{deactivated: true})
+ {: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 "set_info_cache/2" do
+ setup do
+ user = insert(:user)
+ {:ok, user: user}
+ end
+
+ test "update from args", %{user: user} do
+ User.set_info_cache(user, %{following_count: 15, follower_count: 18})
+
+ %{follower_count: followers, following_count: following} = User.get_cached_user_info(user)
+ assert followers == 18
+ assert following == 15
+ end
+
+ test "without args", %{user: user} do
+ User.set_info_cache(user, %{})
+
+ %{follower_count: followers, following_count: following} = User.get_cached_user_info(user)
+ assert followers == 0
+ assert following == 0
+ end
+ end
+
+ describe "user_info/2" do
+ setup do
+ user = insert(:user)
+ {:ok, user: user}
+ end
+
+ test "update from args", %{user: user} do
+ %{follower_count: followers, following_count: following} =
+ User.user_info(user, %{following_count: 15, follower_count: 18})
+
+ assert followers == 18
+ assert following == 15
+ end
+
+ test "without args", %{user: user} do
+ %{follower_count: followers, following_count: following} = User.user_info(user)
+
+ assert followers == 0
+ assert following == 0
+ 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
+ end
+
+ describe "following/followers synchronization" do
+ setup do
+ sync = Pleroma.Config.get([:instance, :external_user_synchronization])
+ on_exit(fn -> Pleroma.Config.put([:instance, :external_user_synchronization], sync) end)
+ end
+
+ test "updates the counters normally on following/getting a follow when disabled" do
+ Pleroma.Config.put([:instance, :external_user_synchronization], false)
+ user = insert(:user)
+
+ other_user =
+ insert(:user,
+ local: false,
+ follower_address: "http://localhost:4001/users/masto_closed/followers",
+ following_address: "http://localhost:4001/users/masto_closed/following",
+ info: %{ap_enabled: true}
+ )
+
+ assert User.user_info(other_user).following_count == 0
+ assert User.user_info(other_user).follower_count == 0
+
+ {:ok, user} = Pleroma.User.follow(user, other_user)
+ other_user = Pleroma.User.get_by_id(other_user.id)
+
+ assert User.user_info(user).following_count == 1
+ assert User.user_info(other_user).follower_count == 1
+ end
+
+ test "syncronizes the counters with the remote instance for the followed when enabled" do
+ Pleroma.Config.put([:instance, :external_user_synchronization], false)
+
+ user = insert(:user)
+
+ other_user =
+ insert(:user,
+ local: false,
+ follower_address: "http://localhost:4001/users/masto_closed/followers",
+ following_address: "http://localhost:4001/users/masto_closed/following",
+ info: %{ap_enabled: true}
+ )
+
+ assert User.user_info(other_user).following_count == 0
+ assert User.user_info(other_user).follower_count == 0
+
+ Pleroma.Config.put([:instance, :external_user_synchronization], true)
+ {:ok, _user} = User.follow(user, other_user)
+ other_user = User.get_by_id(other_user.id)
+
+ assert User.user_info(other_user).follower_count == 437
+ end
+
+ test "syncronizes the counters with the remote instance for the follower when enabled" do
+ Pleroma.Config.put([:instance, :external_user_synchronization], false)
+
+ user = insert(:user)
+
+ other_user =
+ insert(:user,
+ local: false,
+ follower_address: "http://localhost:4001/users/masto_closed/followers",
+ following_address: "http://localhost:4001/users/masto_closed/following",
+ info: %{ap_enabled: true}
+ )
+
+ assert User.user_info(other_user).following_count == 0
+ assert User.user_info(other_user).follower_count == 0
+
+ Pleroma.Config.put([:instance, :external_user_synchronization], true)
+ {:ok, other_user} = User.follow(other_user, user)
+
+ assert User.user_info(other_user).following_count == 152
+ end
+ end
end