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 "repeating a status" do
232 user = insert(:user)
233 other_user = insert(:user)
234
235 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
236
237 {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
238 end
239
240 test "repeating a status privately" do
241 user = insert(:user)
242 other_user = insert(:user)
243
244 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
245
246 {:ok, %Activity{} = announce_activity, _} =
247 CommonAPI.repeat(activity.id, user, %{"visibility" => "private"})
248
249 assert Visibility.is_private?(announce_activity)
250 end
251
252 test "favoriting a status" do
253 user = insert(:user)
254 other_user = insert(:user)
255
256 {:ok, post_activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
257
258 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
259 assert data["type"] == "Like"
260 assert data["actor"] == user.ap_id
261 assert data["object"] == post_activity.data["object"]
262 end
263
264 test "retweeting a status twice returns an error" do
265 user = insert(:user)
266 other_user = insert(:user)
267
268 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
269 {:ok, %Activity{}, _object} = CommonAPI.repeat(activity.id, user)
270 {:error, _} = CommonAPI.repeat(activity.id, user)
271 end
272
273 test "favoriting a status twice returns an error" do
274 user = insert(:user)
275 other_user = insert(:user)
276
277 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
278 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
279
280 assert capture_log(fn ->
281 assert {:error, _} = CommonAPI.favorite(user, activity.id)
282 end) =~ "[error]"
283 end
284 end
285
286 describe "pinned statuses" do
287 setup do
288 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
289
290 user = insert(:user)
291 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
292
293 [user: user, activity: activity]
294 end
295
296 test "pin status", %{user: user, activity: activity} do
297 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
298
299 id = activity.id
300 user = refresh_record(user)
301
302 assert %User{pinned_activities: [^id]} = user
303 end
304
305 test "unlisted statuses can be pinned", %{user: user} do
306 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!", "visibility" => "unlisted"})
307 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
308 end
309
310 test "only self-authored can be pinned", %{activity: activity} do
311 user = insert(:user)
312
313 assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
314 end
315
316 test "max pinned statuses", %{user: user, activity: activity_one} do
317 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
318
319 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
320
321 user = refresh_record(user)
322
323 assert {:error, "You have already pinned the maximum number of statuses"} =
324 CommonAPI.pin(activity_two.id, user)
325 end
326
327 test "unpin status", %{user: user, activity: activity} do
328 {:ok, activity} = CommonAPI.pin(activity.id, user)
329
330 user = refresh_record(user)
331
332 assert {:ok, ^activity} = CommonAPI.unpin(activity.id, user)
333
334 user = refresh_record(user)
335
336 assert %User{pinned_activities: []} = user
337 end
338
339 test "should unpin when deleting a status", %{user: user, activity: activity} do
340 {:ok, activity} = CommonAPI.pin(activity.id, user)
341
342 user = refresh_record(user)
343
344 assert {:ok, _} = CommonAPI.delete(activity.id, user)
345
346 user = refresh_record(user)
347
348 assert %User{pinned_activities: []} = user
349 end
350 end
351
352 describe "mute tests" do
353 setup do
354 user = insert(:user)
355
356 activity = insert(:note_activity)
357
358 [user: user, activity: activity]
359 end
360
361 test "add mute", %{user: user, activity: activity} do
362 {:ok, _} = CommonAPI.add_mute(user, activity)
363 assert CommonAPI.thread_muted?(user, activity)
364 end
365
366 test "remove mute", %{user: user, activity: activity} do
367 CommonAPI.add_mute(user, activity)
368 {:ok, _} = CommonAPI.remove_mute(user, activity)
369 refute CommonAPI.thread_muted?(user, activity)
370 end
371
372 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
373 CommonAPI.add_mute(user, activity)
374 {:error, _} = CommonAPI.add_mute(user, activity)
375 end
376 end
377
378 describe "reports" do
379 test "creates a report" do
380 reporter = insert(:user)
381 target_user = insert(:user)
382
383 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
384
385 reporter_ap_id = reporter.ap_id
386 target_ap_id = target_user.ap_id
387 activity_ap_id = activity.data["id"]
388 comment = "foobar"
389
390 report_data = %{
391 "account_id" => target_user.id,
392 "comment" => comment,
393 "status_ids" => [activity.id]
394 }
395
396 note_obj = %{
397 "type" => "Note",
398 "id" => activity_ap_id,
399 "content" => "foobar",
400 "published" => activity.object.data["published"],
401 "actor" => AccountView.render("show.json", %{user: target_user})
402 }
403
404 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
405
406 assert %Activity{
407 actor: ^reporter_ap_id,
408 data: %{
409 "type" => "Flag",
410 "content" => ^comment,
411 "object" => [^target_ap_id, ^note_obj],
412 "state" => "open"
413 }
414 } = flag_activity
415 end
416
417 test "updates report state" do
418 [reporter, target_user] = insert_pair(:user)
419 activity = insert(:note_activity, user: target_user)
420
421 {:ok, %Activity{id: report_id}} =
422 CommonAPI.report(reporter, %{
423 "account_id" => target_user.id,
424 "comment" => "I feel offended",
425 "status_ids" => [activity.id]
426 })
427
428 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
429
430 assert report.data["state"] == "resolved"
431
432 [reported_user, activity_id] = report.data["object"]
433
434 assert reported_user == target_user.ap_id
435 assert activity_id == activity.data["id"]
436 end
437
438 test "does not update report state when state is unsupported" do
439 [reporter, target_user] = insert_pair(:user)
440 activity = insert(:note_activity, user: target_user)
441
442 {:ok, %Activity{id: report_id}} =
443 CommonAPI.report(reporter, %{
444 "account_id" => target_user.id,
445 "comment" => "I feel offended",
446 "status_ids" => [activity.id]
447 })
448
449 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
450 end
451 end
452
453 describe "reblog muting" do
454 setup do
455 muter = insert(:user)
456
457 muted = insert(:user)
458
459 [muter: muter, muted: muted]
460 end
461
462 test "add a reblog mute", %{muter: muter, muted: muted} do
463 {:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
464
465 assert User.showing_reblogs?(muter, muted) == false
466 end
467
468 test "remove a reblog mute", %{muter: muter, muted: muted} do
469 {:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
470 {:ok, muter} = CommonAPI.show_reblogs(muter, muted)
471
472 assert User.showing_reblogs?(muter, muted) == true
473 end
474 end
475
476 describe "unfollow/2" do
477 test "also unsubscribes a user" do
478 [follower, followed] = insert_pair(:user)
479 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
480 {:ok, followed} = User.subscribe(follower, followed)
481
482 assert User.subscribed_to?(follower, followed)
483
484 {:ok, follower} = CommonAPI.unfollow(follower, followed)
485
486 refute User.subscribed_to?(follower, followed)
487 end
488 end
489
490 describe "accept_follow_request/2" do
491 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
492 user = insert(:user, locked: true)
493 follower = insert(:user)
494 follower_two = insert(:user)
495
496 {:ok, follow_activity} = ActivityPub.follow(follower, user)
497 {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
498 {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
499
500 assert follow_activity.data["state"] == "pending"
501 assert follow_activity_two.data["state"] == "pending"
502 assert follow_activity_three.data["state"] == "pending"
503
504 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
505
506 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
507 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
508 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
509 end
510
511 test "after rejection, it sets all existing pending follow request states to 'reject'" do
512 user = insert(:user, locked: true)
513 follower = insert(:user)
514 follower_two = insert(:user)
515
516 {:ok, follow_activity} = ActivityPub.follow(follower, user)
517 {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
518 {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
519
520 assert follow_activity.data["state"] == "pending"
521 assert follow_activity_two.data["state"] == "pending"
522 assert follow_activity_three.data["state"] == "pending"
523
524 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
525
526 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
527 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
528 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
529 end
530 end
531
532 describe "vote/3" do
533 test "does not allow to vote twice" do
534 user = insert(:user)
535 other_user = insert(:user)
536
537 {:ok, activity} =
538 CommonAPI.post(user, %{
539 "status" => "Am I cute?",
540 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
541 })
542
543 object = Object.normalize(activity)
544
545 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
546
547 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
548 end
549 end
550
551 describe "listen/2" do
552 test "returns a valid activity" do
553 user = insert(:user)
554
555 {:ok, activity} =
556 CommonAPI.listen(user, %{
557 "title" => "lain radio episode 1",
558 "album" => "lain radio",
559 "artist" => "lain",
560 "length" => 180_000
561 })
562
563 object = Object.normalize(activity)
564
565 assert object.data["title"] == "lain radio episode 1"
566
567 assert Visibility.get_visibility(activity) == "public"
568 end
569
570 test "respects visibility=private" do
571 user = insert(:user)
572
573 {:ok, activity} =
574 CommonAPI.listen(user, %{
575 "title" => "lain radio episode 1",
576 "album" => "lain radio",
577 "artist" => "lain",
578 "length" => 180_000,
579 "visibility" => "private"
580 })
581
582 object = Object.normalize(activity)
583
584 assert object.data["title"] == "lain radio episode 1"
585
586 assert Visibility.get_visibility(activity) == "private"
587 end
588 end
589 end