1984aac8d1bd06acb436a575a5d7a21107311524
[akkoma] / test / web / common_api / common_api_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.CommonAPITest do
6 use Pleroma.DataCase
7 alias Pleroma.Activity
8 alias Pleroma.Chat
9 alias Pleroma.Conversation.Participation
10 alias Pleroma.Object
11 alias Pleroma.User
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Visibility
14 alias Pleroma.Web.AdminAPI.AccountView
15 alias Pleroma.Web.CommonAPI
16
17 import Pleroma.Factory
18
19 require Pleroma.Constants
20
21 setup do: clear_config([:instance, :safe_dm_mentions])
22 setup do: clear_config([:instance, :limit])
23 setup do: clear_config([:instance, :max_pinned_statuses])
24
25 describe "posting chat messages" do
26 test "it posts a chat message" do
27 author = insert(:user)
28 recipient = insert(:user)
29
30 {:ok, activity} =
31 CommonAPI.post_chat_message(
32 author,
33 recipient,
34 "a test message <script>alert('uuu')</script>"
35 )
36
37 assert activity.data["type"] == "Create"
38 assert activity.local
39 object = Object.normalize(activity)
40
41 assert object.data["type"] == "ChatMessage"
42 assert object.data["to"] == [recipient.ap_id]
43
44 assert object.data["content"] ==
45 "a test message &lt;script&gt;alert(&#39;uuu&#39;)&lt;/script&gt;"
46
47 assert Chat.get(author.id, recipient.ap_id)
48 assert Chat.get(recipient.id, author.ap_id)
49 end
50 end
51
52 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
53 user = insert(:user)
54 {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
55
56 [participation] = Participation.for_user(user)
57
58 {:ok, convo_reply} =
59 CommonAPI.post(user, %{"status" => ".", "in_reply_to_conversation_id" => participation.id})
60
61 assert Visibility.is_direct?(convo_reply)
62
63 assert activity.data["context"] == convo_reply.data["context"]
64 end
65
66 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
67 har = insert(:user)
68 jafnhar = insert(:user)
69 tridi = insert(:user)
70
71 {:ok, activity} =
72 CommonAPI.post(har, %{
73 "status" => "@#{jafnhar.nickname} hey",
74 "visibility" => "direct"
75 })
76
77 assert har.ap_id in activity.recipients
78 assert jafnhar.ap_id in activity.recipients
79
80 [participation] = Participation.for_user(har)
81
82 {:ok, activity} =
83 CommonAPI.post(har, %{
84 "status" => "I don't really like @#{tridi.nickname}",
85 "visibility" => "direct",
86 "in_reply_to_status_id" => activity.id,
87 "in_reply_to_conversation_id" => participation.id
88 })
89
90 assert har.ap_id in activity.recipients
91 assert jafnhar.ap_id in activity.recipients
92 refute tridi.ap_id in activity.recipients
93 end
94
95 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
96 har = insert(:user)
97 jafnhar = insert(:user)
98 tridi = insert(:user)
99
100 Pleroma.Config.put([:instance, :safe_dm_mentions], true)
101
102 {:ok, activity} =
103 CommonAPI.post(har, %{
104 "status" => "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
105 "visibility" => "direct"
106 })
107
108 refute tridi.ap_id in activity.recipients
109 assert jafnhar.ap_id in activity.recipients
110 end
111
112 test "it de-duplicates tags" do
113 user = insert(:user)
114 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"})
115
116 object = Object.normalize(activity)
117
118 assert object.data["tag"] == ["2hu"]
119 end
120
121 test "it adds emoji in the object" do
122 user = insert(:user)
123 {:ok, activity} = CommonAPI.post(user, %{"status" => ":firefox:"})
124
125 assert Object.normalize(activity).data["emoji"]["firefox"]
126 end
127
128 describe "posting" do
129 test "it supports explicit addressing" do
130 user = insert(:user)
131 user_two = insert(:user)
132 user_three = insert(:user)
133 user_four = insert(:user)
134
135 {:ok, activity} =
136 CommonAPI.post(user, %{
137 "status" =>
138 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
139 "to" => [user_two.nickname, user_four.nickname, "nonexistent"]
140 })
141
142 assert user.ap_id in activity.recipients
143 assert user_two.ap_id in activity.recipients
144 assert user_four.ap_id in activity.recipients
145 refute user_three.ap_id in activity.recipients
146 end
147
148 test "it filters out obviously bad tags when accepting a post as HTML" do
149 user = insert(:user)
150
151 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
152
153 {:ok, activity} =
154 CommonAPI.post(user, %{
155 "status" => post,
156 "content_type" => "text/html"
157 })
158
159 object = Object.normalize(activity)
160
161 assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
162 end
163
164 test "it filters out obviously bad tags when accepting a post as Markdown" do
165 user = insert(:user)
166
167 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
168
169 {:ok, activity} =
170 CommonAPI.post(user, %{
171 "status" => post,
172 "content_type" => "text/markdown"
173 })
174
175 object = Object.normalize(activity)
176
177 assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
178 end
179
180 test "it does not allow replies to direct messages that are not direct messages themselves" do
181 user = insert(:user)
182
183 {:ok, activity} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
184
185 assert {:ok, _} =
186 CommonAPI.post(user, %{
187 "status" => "suya..",
188 "visibility" => "direct",
189 "in_reply_to_status_id" => activity.id
190 })
191
192 Enum.each(["public", "private", "unlisted"], fn visibility ->
193 assert {:error, "The message visibility must be direct"} =
194 CommonAPI.post(user, %{
195 "status" => "suya..",
196 "visibility" => visibility,
197 "in_reply_to_status_id" => activity.id
198 })
199 end)
200 end
201
202 test "it allows to address a list" do
203 user = insert(:user)
204 {:ok, list} = Pleroma.List.create("foo", user)
205
206 {:ok, activity} =
207 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
208
209 assert activity.data["bcc"] == [list.ap_id]
210 assert activity.recipients == [list.ap_id, user.ap_id]
211 assert activity.data["listMessage"] == list.ap_id
212 end
213
214 test "it returns error when status is empty and no attachments" do
215 user = insert(:user)
216
217 assert {:error, "Cannot post an empty status without attachments"} =
218 CommonAPI.post(user, %{"status" => ""})
219 end
220
221 test "it validates character limits are correctly enforced" do
222 Pleroma.Config.put([:instance, :limit], 5)
223
224 user = insert(:user)
225
226 assert {:error, "The status is over the character limit"} =
227 CommonAPI.post(user, %{"status" => "foobar"})
228
229 assert {:ok, activity} = CommonAPI.post(user, %{"status" => "12345"})
230 end
231
232 test "it can handle activities that expire" do
233 user = insert(:user)
234
235 expires_at =
236 NaiveDateTime.utc_now()
237 |> NaiveDateTime.truncate(:second)
238 |> NaiveDateTime.add(1_000_000, :second)
239
240 assert {:ok, activity} =
241 CommonAPI.post(user, %{"status" => "chai", "expires_in" => 1_000_000})
242
243 assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id)
244 assert expiration.scheduled_at == expires_at
245 end
246 end
247
248 describe "reactions" do
249 test "reacting to a status with an emoji" do
250 user = insert(:user)
251 other_user = insert(:user)
252
253 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
254
255 {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍")
256
257 assert reaction.data["actor"] == user.ap_id
258 assert reaction.data["content"] == "👍"
259
260 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
261
262 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
263 end
264
265 test "unreacting to a status with an emoji" do
266 user = insert(:user)
267 other_user = insert(:user)
268
269 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
270 {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍")
271
272 {:ok, unreaction, _} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
273
274 assert unreaction.data["type"] == "Undo"
275 assert unreaction.data["object"] == reaction.data["id"]
276 end
277
278 test "repeating a status" do
279 user = insert(:user)
280 other_user = insert(:user)
281
282 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
283
284 {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
285 end
286
287 test "repeating a status privately" do
288 user = insert(:user)
289 other_user = insert(:user)
290
291 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
292
293 {:ok, %Activity{} = announce_activity, _} =
294 CommonAPI.repeat(activity.id, user, %{"visibility" => "private"})
295
296 assert Visibility.is_private?(announce_activity)
297 end
298
299 test "favoriting a status" do
300 user = insert(:user)
301 other_user = insert(:user)
302
303 {:ok, post_activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
304
305 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
306 assert data["type"] == "Like"
307 assert data["actor"] == user.ap_id
308 assert data["object"] == post_activity.data["object"]
309 end
310
311 test "retweeting a status twice returns the status" do
312 user = insert(:user)
313 other_user = insert(:user)
314
315 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
316 {:ok, %Activity{} = activity, object} = CommonAPI.repeat(activity.id, user)
317 {:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user)
318 end
319
320 test "favoriting a status twice returns ok, but without the like activity" do
321 user = insert(:user)
322 other_user = insert(:user)
323
324 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
325 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
326 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
327 end
328 end
329
330 describe "pinned statuses" do
331 setup do
332 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
333
334 user = insert(:user)
335 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
336
337 [user: user, activity: activity]
338 end
339
340 test "pin status", %{user: user, activity: activity} do
341 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
342
343 id = activity.id
344 user = refresh_record(user)
345
346 assert %User{pinned_activities: [^id]} = user
347 end
348
349 test "pin poll", %{user: user} do
350 {:ok, activity} =
351 CommonAPI.post(user, %{
352 "status" => "How is fediverse today?",
353 "poll" => %{"options" => ["Absolutely outstanding", "Not good"], "expires_in" => 20}
354 })
355
356 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
357
358 id = activity.id
359 user = refresh_record(user)
360
361 assert %User{pinned_activities: [^id]} = user
362 end
363
364 test "unlisted statuses can be pinned", %{user: user} do
365 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!", "visibility" => "unlisted"})
366 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
367 end
368
369 test "only self-authored can be pinned", %{activity: activity} do
370 user = insert(:user)
371
372 assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
373 end
374
375 test "max pinned statuses", %{user: user, activity: activity_one} do
376 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
377
378 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
379
380 user = refresh_record(user)
381
382 assert {:error, "You have already pinned the maximum number of statuses"} =
383 CommonAPI.pin(activity_two.id, user)
384 end
385
386 test "unpin status", %{user: user, activity: activity} do
387 {:ok, activity} = CommonAPI.pin(activity.id, user)
388
389 user = refresh_record(user)
390
391 assert {:ok, ^activity} = CommonAPI.unpin(activity.id, user)
392
393 user = refresh_record(user)
394
395 assert %User{pinned_activities: []} = user
396 end
397
398 test "should unpin when deleting a status", %{user: user, activity: activity} do
399 {:ok, activity} = CommonAPI.pin(activity.id, user)
400
401 user = refresh_record(user)
402
403 assert {:ok, _} = CommonAPI.delete(activity.id, user)
404
405 user = refresh_record(user)
406
407 assert %User{pinned_activities: []} = user
408 end
409 end
410
411 describe "mute tests" do
412 setup do
413 user = insert(:user)
414
415 activity = insert(:note_activity)
416
417 [user: user, activity: activity]
418 end
419
420 test "add mute", %{user: user, activity: activity} do
421 {:ok, _} = CommonAPI.add_mute(user, activity)
422 assert CommonAPI.thread_muted?(user, activity)
423 end
424
425 test "remove mute", %{user: user, activity: activity} do
426 CommonAPI.add_mute(user, activity)
427 {:ok, _} = CommonAPI.remove_mute(user, activity)
428 refute CommonAPI.thread_muted?(user, activity)
429 end
430
431 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
432 CommonAPI.add_mute(user, activity)
433 {:error, _} = CommonAPI.add_mute(user, activity)
434 end
435 end
436
437 describe "reports" do
438 test "creates a report" do
439 reporter = insert(:user)
440 target_user = insert(:user)
441
442 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
443
444 reporter_ap_id = reporter.ap_id
445 target_ap_id = target_user.ap_id
446 activity_ap_id = activity.data["id"]
447 comment = "foobar"
448
449 report_data = %{
450 "account_id" => target_user.id,
451 "comment" => comment,
452 "status_ids" => [activity.id]
453 }
454
455 note_obj = %{
456 "type" => "Note",
457 "id" => activity_ap_id,
458 "content" => "foobar",
459 "published" => activity.object.data["published"],
460 "actor" => AccountView.render("show.json", %{user: target_user})
461 }
462
463 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
464
465 assert %Activity{
466 actor: ^reporter_ap_id,
467 data: %{
468 "type" => "Flag",
469 "content" => ^comment,
470 "object" => [^target_ap_id, ^note_obj],
471 "state" => "open"
472 }
473 } = flag_activity
474 end
475
476 test "updates report state" do
477 [reporter, target_user] = insert_pair(:user)
478 activity = insert(:note_activity, user: target_user)
479
480 {:ok, %Activity{id: report_id}} =
481 CommonAPI.report(reporter, %{
482 "account_id" => target_user.id,
483 "comment" => "I feel offended",
484 "status_ids" => [activity.id]
485 })
486
487 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
488
489 assert report.data["state"] == "resolved"
490
491 [reported_user, activity_id] = report.data["object"]
492
493 assert reported_user == target_user.ap_id
494 assert activity_id == activity.data["id"]
495 end
496
497 test "does not update report state when state is unsupported" do
498 [reporter, target_user] = insert_pair(:user)
499 activity = insert(:note_activity, user: target_user)
500
501 {:ok, %Activity{id: report_id}} =
502 CommonAPI.report(reporter, %{
503 "account_id" => target_user.id,
504 "comment" => "I feel offended",
505 "status_ids" => [activity.id]
506 })
507
508 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
509 end
510
511 test "updates state of multiple reports" do
512 [reporter, target_user] = insert_pair(:user)
513 activity = insert(:note_activity, user: target_user)
514
515 {:ok, %Activity{id: first_report_id}} =
516 CommonAPI.report(reporter, %{
517 "account_id" => target_user.id,
518 "comment" => "I feel offended",
519 "status_ids" => [activity.id]
520 })
521
522 {:ok, %Activity{id: second_report_id}} =
523 CommonAPI.report(reporter, %{
524 "account_id" => target_user.id,
525 "comment" => "I feel very offended!",
526 "status_ids" => [activity.id]
527 })
528
529 {:ok, report_ids} =
530 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
531
532 first_report = Activity.get_by_id(first_report_id)
533 second_report = Activity.get_by_id(second_report_id)
534
535 assert report_ids -- [first_report_id, second_report_id] == []
536 assert first_report.data["state"] == "resolved"
537 assert second_report.data["state"] == "resolved"
538 end
539 end
540
541 describe "reblog muting" do
542 setup do
543 muter = insert(:user)
544
545 muted = insert(:user)
546
547 [muter: muter, muted: muted]
548 end
549
550 test "add a reblog mute", %{muter: muter, muted: muted} do
551 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
552
553 assert User.showing_reblogs?(muter, muted) == false
554 end
555
556 test "remove a reblog mute", %{muter: muter, muted: muted} do
557 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
558 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
559
560 assert User.showing_reblogs?(muter, muted) == true
561 end
562 end
563
564 describe "unfollow/2" do
565 test "also unsubscribes a user" do
566 [follower, followed] = insert_pair(:user)
567 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
568 {:ok, _subscription} = User.subscribe(follower, followed)
569
570 assert User.subscribed_to?(follower, followed)
571
572 {:ok, follower} = CommonAPI.unfollow(follower, followed)
573
574 refute User.subscribed_to?(follower, followed)
575 end
576
577 test "cancels a pending follow for a local user" do
578 follower = insert(:user)
579 followed = insert(:user, locked: true)
580
581 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
582 CommonAPI.follow(follower, followed)
583
584 assert User.get_follow_state(follower, followed) == :follow_pending
585 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
586 assert User.get_follow_state(follower, followed) == nil
587
588 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
589 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
590
591 assert %{
592 data: %{
593 "type" => "Undo",
594 "object" => %{"type" => "Follow", "state" => "cancelled"}
595 }
596 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
597 end
598
599 test "cancels a pending follow for a remote user" do
600 follower = insert(:user)
601 followed = insert(:user, locked: true, local: false, ap_enabled: true)
602
603 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
604 CommonAPI.follow(follower, followed)
605
606 assert User.get_follow_state(follower, followed) == :follow_pending
607 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
608 assert User.get_follow_state(follower, followed) == nil
609
610 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
611 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
612
613 assert %{
614 data: %{
615 "type" => "Undo",
616 "object" => %{"type" => "Follow", "state" => "cancelled"}
617 }
618 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
619 end
620 end
621
622 describe "accept_follow_request/2" do
623 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
624 user = insert(:user, locked: true)
625 follower = insert(:user)
626 follower_two = insert(:user)
627
628 {:ok, follow_activity} = ActivityPub.follow(follower, user)
629 {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
630 {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
631
632 assert follow_activity.data["state"] == "pending"
633 assert follow_activity_two.data["state"] == "pending"
634 assert follow_activity_three.data["state"] == "pending"
635
636 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
637
638 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
639 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
640 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
641 end
642
643 test "after rejection, it sets all existing pending follow request states to 'reject'" do
644 user = insert(:user, locked: true)
645 follower = insert(:user)
646 follower_two = insert(:user)
647
648 {:ok, follow_activity} = ActivityPub.follow(follower, user)
649 {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
650 {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
651
652 assert follow_activity.data["state"] == "pending"
653 assert follow_activity_two.data["state"] == "pending"
654 assert follow_activity_three.data["state"] == "pending"
655
656 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
657
658 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
659 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
660 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
661 end
662 end
663
664 describe "vote/3" do
665 test "does not allow to vote twice" do
666 user = insert(:user)
667 other_user = insert(:user)
668
669 {:ok, activity} =
670 CommonAPI.post(user, %{
671 "status" => "Am I cute?",
672 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
673 })
674
675 object = Object.normalize(activity)
676
677 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
678
679 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
680 end
681 end
682
683 describe "listen/2" do
684 test "returns a valid activity" do
685 user = insert(:user)
686
687 {:ok, activity} =
688 CommonAPI.listen(user, %{
689 "title" => "lain radio episode 1",
690 "album" => "lain radio",
691 "artist" => "lain",
692 "length" => 180_000
693 })
694
695 object = Object.normalize(activity)
696
697 assert object.data["title"] == "lain radio episode 1"
698
699 assert Visibility.get_visibility(activity) == "public"
700 end
701
702 test "respects visibility=private" do
703 user = insert(:user)
704
705 {:ok, activity} =
706 CommonAPI.listen(user, %{
707 "title" => "lain radio episode 1",
708 "album" => "lain radio",
709 "artist" => "lain",
710 "length" => 180_000,
711 "visibility" => "private"
712 })
713
714 object = Object.normalize(activity)
715
716 assert object.data["title"] == "lain radio episode 1"
717
718 assert Visibility.get_visibility(activity) == "private"
719 end
720 end
721 end