[#483] Refactored blocks and follows import, added tests.
[akkoma] / test / user_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.UserTest do
6 alias Pleroma.Builders.UserBuilder
7 alias Pleroma.{User, Repo, Activity}
8 alias Pleroma.Web.CommonAPI
9 use Pleroma.DataCase
10
11 import Pleroma.Factory
12
13 setup_all do
14 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
15 :ok
16 end
17
18 describe "when tags are nil" do
19 test "tagging a user" do
20 user = insert(:user, %{tags: nil})
21 user = User.tag(user, ["cool", "dude"])
22
23 assert "cool" in user.tags
24 assert "dude" in user.tags
25 end
26
27 test "untagging a user" do
28 user = insert(:user, %{tags: nil})
29 user = User.untag(user, ["cool", "dude"])
30
31 assert user.tags == []
32 end
33 end
34
35 test "ap_id returns the activity pub id for the user" do
36 user = UserBuilder.build()
37
38 expected_ap_id = "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
39
40 assert expected_ap_id == User.ap_id(user)
41 end
42
43 test "ap_followers returns the followers collection for the user" do
44 user = UserBuilder.build()
45
46 expected_followers_collection = "#{User.ap_id(user)}/followers"
47
48 assert expected_followers_collection == User.ap_followers(user)
49 end
50
51 test "follow takes a user and another user" do
52 user = insert(:user)
53 followed = insert(:user)
54
55 {:ok, user} = User.follow(user, followed)
56
57 user = Repo.get(User, user.id)
58
59 followed = User.get_by_ap_id(followed.ap_id)
60 assert followed.info.follower_count == 1
61
62 assert User.ap_followers(followed) in user.following
63 end
64
65 test "can't follow a deactivated users" do
66 user = insert(:user)
67 followed = insert(:user, info: %{deactivated: true})
68
69 {:error, _} = User.follow(user, followed)
70 end
71
72 test "can't follow a user who blocked us" do
73 blocker = insert(:user)
74 blockee = insert(:user)
75
76 {:ok, blocker} = User.block(blocker, blockee)
77
78 {:error, _} = User.follow(blockee, blocker)
79 end
80
81 test "local users do not automatically follow local locked accounts" do
82 follower = insert(:user, info: %{locked: true})
83 followed = insert(:user, info: %{locked: true})
84
85 {:ok, follower} = User.maybe_direct_follow(follower, followed)
86
87 refute User.following?(follower, followed)
88 end
89
90 # This is a somewhat useless test.
91 # test "following a remote user will ensure a websub subscription is present" do
92 # user = insert(:user)
93 # {:ok, followed} = OStatus.make_user("shp@social.heldscal.la")
94
95 # assert followed.local == false
96
97 # {:ok, user} = User.follow(user, followed)
98 # assert User.ap_followers(followed) in user.following
99
100 # query = from w in WebsubClientSubscription,
101 # where: w.topic == ^followed.info["topic"]
102 # websub = Repo.one(query)
103
104 # assert websub
105 # end
106
107 test "unfollow takes a user and another user" do
108 followed = insert(:user)
109 user = insert(:user, %{following: [User.ap_followers(followed)]})
110
111 {:ok, user, _activity} = User.unfollow(user, followed)
112
113 user = Repo.get(User, user.id)
114
115 assert user.following == []
116 end
117
118 test "unfollow doesn't unfollow yourself" do
119 user = insert(:user)
120
121 {:error, _} = User.unfollow(user, user)
122
123 user = Repo.get(User, user.id)
124 assert user.following == [user.ap_id]
125 end
126
127 test "test if a user is following another user" do
128 followed = insert(:user)
129 user = insert(:user, %{following: [User.ap_followers(followed)]})
130
131 assert User.following?(user, followed)
132 refute User.following?(followed, user)
133 end
134
135 describe "user registration" do
136 @full_user_data %{
137 bio: "A guy",
138 name: "my name",
139 nickname: "nick",
140 password: "test",
141 password_confirmation: "test",
142 email: "email@example.com"
143 }
144
145 test "it requires an email, name, nickname and password, bio is optional" do
146 @full_user_data
147 |> Map.keys()
148 |> Enum.each(fn key ->
149 params = Map.delete(@full_user_data, key)
150 changeset = User.register_changeset(%User{}, params)
151
152 assert if key == :bio, do: changeset.valid?, else: not changeset.valid?
153 end)
154 end
155
156 test "it restricts certain nicknames" do
157 [restricted_name | _] = Pleroma.Config.get([Pleroma.User, :restricted_nicknames])
158
159 assert is_bitstring(restricted_name)
160
161 params =
162 @full_user_data
163 |> Map.put(:nickname, restricted_name)
164
165 changeset = User.register_changeset(%User{}, params)
166
167 refute changeset.valid?
168 end
169
170 test "it sets the password_hash, ap_id and following fields" do
171 changeset = User.register_changeset(%User{}, @full_user_data)
172
173 assert changeset.valid?
174
175 assert is_binary(changeset.changes[:password_hash])
176 assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
177
178 assert changeset.changes[:following] == [
179 User.ap_followers(%User{nickname: @full_user_data.nickname})
180 ]
181
182 assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
183 end
184
185 test "it ensures info is not nil" do
186 changeset = User.register_changeset(%User{}, @full_user_data)
187
188 assert changeset.valid?
189
190 {:ok, user} =
191 changeset
192 |> Repo.insert()
193
194 refute is_nil(user.info)
195 end
196 end
197
198 describe "user registration, with :account_activation_required" do
199 @full_user_data %{
200 bio: "A guy",
201 name: "my name",
202 nickname: "nick",
203 password: "test",
204 password_confirmation: "test",
205 email: "email@example.com"
206 }
207
208 setup do
209 setting = Pleroma.Config.get([:instance, :account_activation_required])
210
211 unless setting do
212 Pleroma.Config.put([:instance, :account_activation_required], true)
213 on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
214 end
215
216 :ok
217 end
218
219 test "it creates unconfirmed user" do
220 changeset = User.register_changeset(%User{}, @full_user_data)
221 assert changeset.valid?
222
223 {:ok, user} = Repo.insert(changeset)
224
225 assert user.info.confirmation_pending
226 assert user.info.confirmation_token
227 end
228
229 test "it creates confirmed user if :confirmed option is given" do
230 changeset = User.register_changeset(%User{}, @full_user_data, confirmed: true)
231 assert changeset.valid?
232
233 {:ok, user} = Repo.insert(changeset)
234
235 refute user.info.confirmation_pending
236 refute user.info.confirmation_token
237 end
238 end
239
240 describe "get_or_fetch/1" do
241 test "gets an existing user by nickname" do
242 user = insert(:user)
243 fetched_user = User.get_or_fetch(user.nickname)
244
245 assert user == fetched_user
246 end
247
248 test "gets an existing user by ap_id" do
249 ap_id = "http://mastodon.example.org/users/admin"
250
251 user =
252 insert(
253 :user,
254 local: false,
255 nickname: "admin@mastodon.example.org",
256 ap_id: ap_id,
257 info: %{}
258 )
259
260 fetched_user = User.get_or_fetch(ap_id)
261 freshed_user = refresh_record(user)
262 assert freshed_user == fetched_user
263 end
264 end
265
266 describe "fetching a user from nickname or trying to build one" do
267 test "gets an existing user" do
268 user = insert(:user)
269 fetched_user = User.get_or_fetch_by_nickname(user.nickname)
270
271 assert user == fetched_user
272 end
273
274 test "gets an existing user, case insensitive" do
275 user = insert(:user, nickname: "nick")
276 fetched_user = User.get_or_fetch_by_nickname("NICK")
277
278 assert user == fetched_user
279 end
280
281 test "fetches an external user via ostatus if no user exists" do
282 fetched_user = User.get_or_fetch_by_nickname("shp@social.heldscal.la")
283 assert fetched_user.nickname == "shp@social.heldscal.la"
284 end
285
286 test "returns nil if no user could be fetched" do
287 fetched_user = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la")
288 assert fetched_user == nil
289 end
290
291 test "returns nil for nonexistant local user" do
292 fetched_user = User.get_or_fetch_by_nickname("nonexistant")
293 assert fetched_user == nil
294 end
295
296 test "updates an existing user, if stale" do
297 a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800)
298
299 orig_user =
300 insert(
301 :user,
302 local: false,
303 nickname: "admin@mastodon.example.org",
304 ap_id: "http://mastodon.example.org/users/admin",
305 last_refreshed_at: a_week_ago,
306 info: %{}
307 )
308
309 assert orig_user.last_refreshed_at == a_week_ago
310
311 user = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
312 assert user.info.source_data["endpoints"]
313
314 refute user.last_refreshed_at == orig_user.last_refreshed_at
315 end
316 end
317
318 test "returns an ap_id for a user" do
319 user = insert(:user)
320
321 assert User.ap_id(user) ==
322 Pleroma.Web.Router.Helpers.o_status_url(
323 Pleroma.Web.Endpoint,
324 :feed_redirect,
325 user.nickname
326 )
327 end
328
329 test "returns an ap_followers link for a user" do
330 user = insert(:user)
331
332 assert User.ap_followers(user) ==
333 Pleroma.Web.Router.Helpers.o_status_url(
334 Pleroma.Web.Endpoint,
335 :feed_redirect,
336 user.nickname
337 ) <> "/followers"
338 end
339
340 describe "remote user creation changeset" do
341 @valid_remote %{
342 bio: "hello",
343 name: "Someone",
344 nickname: "a@b.de",
345 ap_id: "http...",
346 info: %{some: "info"},
347 avatar: %{some: "avatar"}
348 }
349
350 test "it confirms validity" do
351 cs = User.remote_user_creation(@valid_remote)
352 assert cs.valid?
353 end
354
355 test "it sets the follower_adress" do
356 cs = User.remote_user_creation(@valid_remote)
357 # remote users get a fake local follower address
358 assert cs.changes.follower_address ==
359 User.ap_followers(%User{nickname: @valid_remote[:nickname]})
360 end
361
362 test "it enforces the fqn format for nicknames" do
363 cs = User.remote_user_creation(%{@valid_remote | nickname: "bla"})
364 assert cs.changes.local == false
365 assert cs.changes.avatar
366 refute cs.valid?
367 end
368
369 test "it has required fields" do
370 [:name, :ap_id]
371 |> Enum.each(fn field ->
372 cs = User.remote_user_creation(Map.delete(@valid_remote, field))
373 refute cs.valid?
374 end)
375 end
376
377 test "it restricts some sizes" do
378 [bio: 5000, name: 100]
379 |> Enum.each(fn {field, size} ->
380 string = String.pad_leading(".", size)
381 cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
382 assert cs.valid?
383
384 string = String.pad_leading(".", size + 1)
385 cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
386 refute cs.valid?
387 end)
388 end
389 end
390
391 describe "followers and friends" do
392 test "gets all followers for a given user" do
393 user = insert(:user)
394 follower_one = insert(:user)
395 follower_two = insert(:user)
396 not_follower = insert(:user)
397
398 {:ok, follower_one} = User.follow(follower_one, user)
399 {:ok, follower_two} = User.follow(follower_two, user)
400
401 {:ok, res} = User.get_followers(user)
402
403 assert Enum.member?(res, follower_one)
404 assert Enum.member?(res, follower_two)
405 refute Enum.member?(res, not_follower)
406 end
407
408 test "gets all friends (followed users) for a given user" do
409 user = insert(:user)
410 followed_one = insert(:user)
411 followed_two = insert(:user)
412 not_followed = insert(:user)
413
414 {:ok, user} = User.follow(user, followed_one)
415 {:ok, user} = User.follow(user, followed_two)
416
417 {:ok, res} = User.get_friends(user)
418
419 followed_one = User.get_by_ap_id(followed_one.ap_id)
420 followed_two = User.get_by_ap_id(followed_two.ap_id)
421 assert Enum.member?(res, followed_one)
422 assert Enum.member?(res, followed_two)
423 refute Enum.member?(res, not_followed)
424 end
425 end
426
427 describe "updating note and follower count" do
428 test "it sets the info->note_count property" do
429 note = insert(:note)
430
431 user = User.get_by_ap_id(note.data["actor"])
432
433 assert user.info.note_count == 0
434
435 {:ok, user} = User.update_note_count(user)
436
437 assert user.info.note_count == 1
438 end
439
440 test "it increases the info->note_count property" do
441 note = insert(:note)
442 user = User.get_by_ap_id(note.data["actor"])
443
444 assert user.info.note_count == 0
445
446 {:ok, user} = User.increase_note_count(user)
447
448 assert user.info.note_count == 1
449
450 {:ok, user} = User.increase_note_count(user)
451
452 assert user.info.note_count == 2
453 end
454
455 test "it decreases the info->note_count property" do
456 note = insert(:note)
457 user = User.get_by_ap_id(note.data["actor"])
458
459 assert user.info.note_count == 0
460
461 {:ok, user} = User.increase_note_count(user)
462
463 assert user.info.note_count == 1
464
465 {:ok, user} = User.decrease_note_count(user)
466
467 assert user.info.note_count == 0
468
469 {:ok, user} = User.decrease_note_count(user)
470
471 assert user.info.note_count == 0
472 end
473
474 test "it sets the info->follower_count property" do
475 user = insert(:user)
476 follower = insert(:user)
477
478 User.follow(follower, user)
479
480 assert user.info.follower_count == 0
481
482 {:ok, user} = User.update_follower_count(user)
483
484 assert user.info.follower_count == 1
485 end
486 end
487
488 describe "follow_import" do
489 test "it imports user followings from list" do
490 [user1, user2, user3] = insert_list(3, :user)
491
492 identifiers = [
493 user2.ap_id,
494 user3.nickname
495 ]
496
497 result = User.follow_import(user1, identifiers)
498 assert is_list(result)
499 assert result == [user2, user3]
500 end
501 end
502
503 describe "blocks" do
504 test "it blocks people" do
505 user = insert(:user)
506 blocked_user = insert(:user)
507
508 refute User.blocks?(user, blocked_user)
509
510 {:ok, user} = User.block(user, blocked_user)
511
512 assert User.blocks?(user, blocked_user)
513 end
514
515 test "it unblocks users" do
516 user = insert(:user)
517 blocked_user = insert(:user)
518
519 {:ok, user} = User.block(user, blocked_user)
520 {:ok, user} = User.unblock(user, blocked_user)
521
522 refute User.blocks?(user, blocked_user)
523 end
524
525 test "blocks tear down cyclical follow relationships" do
526 blocker = insert(:user)
527 blocked = insert(:user)
528
529 {:ok, blocker} = User.follow(blocker, blocked)
530 {:ok, blocked} = User.follow(blocked, blocker)
531
532 assert User.following?(blocker, blocked)
533 assert User.following?(blocked, blocker)
534
535 {:ok, blocker} = User.block(blocker, blocked)
536 blocked = Repo.get(User, blocked.id)
537
538 assert User.blocks?(blocker, blocked)
539
540 refute User.following?(blocker, blocked)
541 refute User.following?(blocked, blocker)
542 end
543
544 test "blocks tear down blocker->blocked follow relationships" do
545 blocker = insert(:user)
546 blocked = insert(:user)
547
548 {:ok, blocker} = User.follow(blocker, blocked)
549
550 assert User.following?(blocker, blocked)
551 refute User.following?(blocked, blocker)
552
553 {:ok, blocker} = User.block(blocker, blocked)
554 blocked = Repo.get(User, blocked.id)
555
556 assert User.blocks?(blocker, blocked)
557
558 refute User.following?(blocker, blocked)
559 refute User.following?(blocked, blocker)
560 end
561
562 test "blocks tear down blocked->blocker follow relationships" do
563 blocker = insert(:user)
564 blocked = insert(:user)
565
566 {:ok, blocked} = User.follow(blocked, blocker)
567
568 refute User.following?(blocker, blocked)
569 assert User.following?(blocked, blocker)
570
571 {:ok, blocker} = User.block(blocker, blocked)
572 blocked = Repo.get(User, blocked.id)
573
574 assert User.blocks?(blocker, blocked)
575
576 refute User.following?(blocker, blocked)
577 refute User.following?(blocked, blocker)
578 end
579 end
580
581 describe "domain blocking" do
582 test "blocks domains" do
583 user = insert(:user)
584 collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
585
586 {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com")
587
588 assert User.blocks?(user, collateral_user)
589 end
590
591 test "unblocks domains" do
592 user = insert(:user)
593 collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
594
595 {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com")
596 {:ok, user} = User.unblock_domain(user, "awful-and-rude-instance.com")
597
598 refute User.blocks?(user, collateral_user)
599 end
600 end
601
602 describe "blocks_import" do
603 test "it imports user blocks from list" do
604 [user1, user2, user3] = insert_list(3, :user)
605
606 identifiers = [
607 user2.ap_id,
608 user3.nickname
609 ]
610
611 result = User.blocks_import(user1, identifiers)
612 assert is_list(result)
613 assert result == [user2, user3]
614 end
615 end
616
617 test "get recipients from activity" do
618 actor = insert(:user)
619 user = insert(:user, local: true)
620 user_two = insert(:user, local: false)
621 addressed = insert(:user, local: true)
622 addressed_remote = insert(:user, local: false)
623
624 {:ok, activity} =
625 CommonAPI.post(actor, %{
626 "status" => "hey @#{addressed.nickname} @#{addressed_remote.nickname}"
627 })
628
629 assert [addressed] == User.get_recipients_from_activity(activity)
630
631 {:ok, user} = User.follow(user, actor)
632 {:ok, _user_two} = User.follow(user_two, actor)
633 recipients = User.get_recipients_from_activity(activity)
634 assert length(recipients) == 2
635 assert user in recipients
636 assert addressed in recipients
637 end
638
639 test ".deactivate can de-activate then re-activate a user" do
640 user = insert(:user)
641 assert false == user.info.deactivated
642 {:ok, user} = User.deactivate(user)
643 assert true == user.info.deactivated
644 {:ok, user} = User.deactivate(user, false)
645 assert false == user.info.deactivated
646 end
647
648 test ".delete deactivates a user, all follow relationships and all create activities" do
649 user = insert(:user)
650 followed = insert(:user)
651 follower = insert(:user)
652
653 {:ok, user} = User.follow(user, followed)
654 {:ok, follower} = User.follow(follower, user)
655
656 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
657 {:ok, activity_two} = CommonAPI.post(follower, %{"status" => "3hu"})
658
659 {:ok, _, _} = CommonAPI.favorite(activity_two.id, user)
660 {:ok, _, _} = CommonAPI.favorite(activity.id, follower)
661 {:ok, _, _} = CommonAPI.repeat(activity.id, follower)
662
663 {:ok, _} = User.delete(user)
664
665 followed = Repo.get(User, followed.id)
666 follower = Repo.get(User, follower.id)
667 user = Repo.get(User, user.id)
668
669 assert user.info.deactivated
670
671 refute User.following?(user, followed)
672 refute User.following?(followed, follower)
673
674 # TODO: Remove favorites, repeats, delete activities.
675
676 refute Repo.get(Activity, activity.id)
677 end
678
679 test "get_public_key_for_ap_id fetches a user that's not in the db" do
680 assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin")
681 end
682
683 test "insert or update a user from given data" do
684 user = insert(:user, %{nickname: "nick@name.de"})
685 data = %{ap_id: user.ap_id <> "xxx", name: user.name, nickname: user.nickname}
686
687 assert {:ok, %User{}} = User.insert_or_update_user(data)
688 end
689
690 describe "per-user rich-text filtering" do
691 test "html_filter_policy returns nil when rich-text is enabled" do
692 user = insert(:user)
693
694 assert nil == User.html_filter_policy(user)
695 end
696
697 test "html_filter_policy returns TwitterText scrubber when rich-text is disabled" do
698 user = insert(:user, %{info: %{no_rich_text: true}})
699
700 assert Pleroma.HTML.Scrubber.TwitterText == User.html_filter_policy(user)
701 end
702 end
703
704 describe "caching" do
705 test "invalidate_cache works" do
706 user = insert(:user)
707 _user_info = User.get_cached_user_info(user)
708
709 User.invalidate_cache(user)
710
711 {:ok, nil} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
712 {:ok, nil} = Cachex.get(:user_cache, "nickname:#{user.nickname}")
713 {:ok, nil} = Cachex.get(:user_cache, "user_info:#{user.id}")
714 end
715
716 test "User.delete() plugs any possible zombie objects" do
717 user = insert(:user)
718
719 {:ok, _} = User.delete(user)
720
721 {:ok, cached_user} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
722
723 assert cached_user != user
724
725 {:ok, cached_user} = Cachex.get(:user_cache, "nickname:#{user.ap_id}")
726
727 assert cached_user != user
728 end
729 end
730
731 describe "User.search" do
732 test "finds a user, ranking by similarity" do
733 _user = insert(:user, %{name: "lain"})
734 _user_two = insert(:user, %{name: "ean"})
735 _user_three = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social"})
736 user_four = insert(:user, %{nickname: "lain@pleroma.soykaf.com"})
737
738 assert user_four ==
739 User.search("lain@ple") |> List.first() |> Map.put(:search_distance, nil)
740 end
741 end
742 end