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