total rebase
[anni] / test / pleroma / web / mastodon_api / controllers / status_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
6   use Pleroma.Web.ConnCase, async: false
7   use Oban.Testing, repo: Pleroma.Repo
8
9   alias Pleroma.Activity
10   alias Pleroma.Conversation.Participation
11   alias Pleroma.ModerationLog
12   alias Pleroma.Object
13   alias Pleroma.Repo
14   alias Pleroma.ScheduledActivity
15   alias Pleroma.Tests.Helpers
16   alias Pleroma.Tests.ObanHelpers
17   alias Pleroma.User
18   alias Pleroma.Web.ActivityPub.ActivityPub
19   alias Pleroma.Web.ActivityPub.Utils
20   alias Pleroma.Web.CommonAPI
21   alias Pleroma.Workers.ScheduledActivityWorker
22
23   import Mox
24   import Pleroma.Factory
25
26   setup do: clear_config([:instance, :federating])
27   setup do: clear_config([:instance, :allow_relay])
28   setup do: clear_config([:mrf, :policies])
29   setup do: clear_config([:mrf_keyword, :reject])
30
31   setup do
32     Pleroma.UnstubbedConfigMock
33     |> stub_with(Pleroma.Config)
34
35     Pleroma.StaticStubbedConfigMock
36     |> stub(:get, fn
37       [:rich_media, :enabled] -> false
38       path -> Pleroma.Test.StaticConfig.get(path)
39     end)
40
41     :ok
42   end
43
44   describe "posting statuses" do
45     setup do: oauth_access(["write:statuses"])
46
47     test "posting a status does not increment reblog_count when relaying", %{conn: conn} do
48       clear_config([:instance, :federating], true)
49       clear_config([:instance, :allow_relay], true)
50
51       response =
52         conn
53         |> put_req_header("content-type", "application/json")
54         |> post("/api/v1/statuses", %{
55           "content_type" => "text/plain",
56           "source" => "Pleroma FE",
57           "status" => "Hello world",
58           "visibility" => "public"
59         })
60         |> json_response_and_validate_schema(200)
61
62       assert response["reblogs_count"] == 0
63       ObanHelpers.perform_all()
64
65       response =
66         conn
67         |> get("/api/v1/statuses/#{response["id"]}", %{})
68         |> json_response_and_validate_schema(200)
69
70       assert response["reblogs_count"] == 0
71     end
72
73     test "posting a status", %{conn: conn} do
74       idempotency_key = "Pikachu rocks!"
75
76       conn_one =
77         conn
78         |> put_req_header("content-type", "application/json")
79         |> put_req_header("idempotency-key", idempotency_key)
80         |> post("/api/v1/statuses", %{
81           "status" => "cofe",
82           "spoiler_text" => "2hu",
83           "sensitive" => "0"
84         })
85
86       assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
87                json_response_and_validate_schema(conn_one, 200)
88
89       assert Activity.get_by_id(id)
90
91       conn_two =
92         conn
93         |> put_req_header("content-type", "application/json")
94         |> put_req_header("idempotency-key", idempotency_key)
95         |> post("/api/v1/statuses", %{
96           "status" => "cofe",
97           "spoiler_text" => "2hu",
98           "sensitive" => 0
99         })
100
101       # Idempotency plug response means detection fail
102       assert %{"id" => second_id} = json_response(conn_two, 200)
103       assert id == second_id
104
105       conn_three =
106         conn
107         |> put_req_header("content-type", "application/json")
108         |> post("/api/v1/statuses", %{
109           "status" => "cofe",
110           "spoiler_text" => "2hu",
111           "sensitive" => "False"
112         })
113
114       assert %{"id" => third_id} = json_response_and_validate_schema(conn_three, 200)
115       refute id == third_id
116
117       # An activity that will expire:
118       # 2 hours
119       expires_in = 2 * 60 * 60
120
121       expires_at = DateTime.add(DateTime.utc_now(), expires_in)
122
123       conn_four =
124         conn
125         |> put_req_header("content-type", "application/json")
126         |> post("/api/v1/statuses", %{
127           "status" => "oolong",
128           "expires_in" => expires_in
129         })
130
131       assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
132
133       assert Activity.get_by_id(fourth_id)
134
135       assert_enqueued(
136         worker: Pleroma.Workers.PurgeExpiredActivity,
137         args: %{activity_id: fourth_id},
138         scheduled_at: expires_at
139       )
140     end
141
142     test "posting a quote post", %{conn: conn} do
143       user = insert(:user)
144
145       {:ok, %{id: activity_id} = activity} = CommonAPI.post(user, %{status: "yolo"})
146       %{data: %{"id" => quote_url}} = Object.normalize(activity)
147
148       conn =
149         conn
150         |> put_req_header("content-type", "application/json")
151         |> post("/api/v1/statuses", %{
152           "status" => "indeed",
153           "quote_id" => activity_id
154         })
155
156       assert %{
157                "id" => id,
158                "pleroma" => %{"quote" => %{"id" => ^activity_id}, "quote_url" => ^quote_url}
159              } = json_response_and_validate_schema(conn, 200)
160
161       assert Activity.get_by_id(id)
162     end
163
164     test "it fails to create a status if `expires_in` is less or equal than an hour", %{
165       conn: conn
166     } do
167       # 1 minute
168       expires_in = 1 * 60
169
170       assert %{"error" => "Expiry date is too soon"} =
171                conn
172                |> put_req_header("content-type", "application/json")
173                |> post("/api/v1/statuses", %{
174                  "status" => "oolong",
175                  "expires_in" => expires_in
176                })
177                |> json_response_and_validate_schema(422)
178
179       # 5 minutes
180       expires_in = 5 * 60
181
182       assert %{"error" => "Expiry date is too soon"} =
183                conn
184                |> put_req_header("content-type", "application/json")
185                |> post("/api/v1/statuses", %{
186                  "status" => "oolong",
187                  "expires_in" => expires_in
188                })
189                |> json_response_and_validate_schema(422)
190     end
191
192     test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do
193       clear_config([:mrf_keyword, :reject], ["GNO"])
194       clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
195
196       assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} =
197                conn
198                |> put_req_header("content-type", "application/json")
199                |> post("/api/v1/statuses", %{"status" => "GNO/Linux"})
200                |> json_response_and_validate_schema(422)
201     end
202
203     test "posting an undefined status with an attachment", %{user: user, conn: conn} do
204       file = %Plug.Upload{
205         content_type: "image/jpeg",
206         path: Path.absname("test/fixtures/image.jpg"),
207         filename: "an_image.jpg"
208       }
209
210       {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
211
212       conn =
213         conn
214         |> put_req_header("content-type", "application/json")
215         |> post("/api/v1/statuses", %{
216           "media_ids" => [to_string(upload.id)]
217         })
218
219       assert json_response_and_validate_schema(conn, 200)
220     end
221
222     test "replying to a status", %{user: user, conn: conn} do
223       {:ok, replied_to} = CommonAPI.post(user, %{status: "cofe"})
224
225       conn =
226         conn
227         |> put_req_header("content-type", "application/json")
228         |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
229
230       assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
231
232       activity = Activity.get_by_id(id)
233
234       assert activity.data["context"] == replied_to.data["context"]
235       assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
236     end
237
238     test "replying to a direct message with visibility other than direct", %{
239       user: user,
240       conn: conn
241     } do
242       {:ok, replied_to} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
243
244       Enum.each(["public", "private", "unlisted"], fn visibility ->
245         conn =
246           conn
247           |> put_req_header("content-type", "application/json")
248           |> post("/api/v1/statuses", %{
249             "status" => "@#{user.nickname} hey",
250             "in_reply_to_id" => replied_to.id,
251             "visibility" => visibility
252           })
253
254         assert json_response_and_validate_schema(conn, 422) == %{
255                  "error" => "The message visibility must be direct"
256                }
257       end)
258     end
259
260     test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
261       conn =
262         conn
263         |> put_req_header("content-type", "application/json")
264         |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
265
266       assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
267       assert Activity.get_by_id(id)
268     end
269
270     test "posting a sensitive status", %{conn: conn} do
271       conn =
272         conn
273         |> put_req_header("content-type", "application/json")
274         |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
275
276       assert %{"content" => "cofe", "id" => id, "sensitive" => true} =
277                json_response_and_validate_schema(conn, 200)
278
279       assert Activity.get_by_id(id)
280     end
281
282     test "posting a fake status", %{conn: conn} do
283       real_conn =
284         conn
285         |> put_req_header("content-type", "application/json")
286         |> post("/api/v1/statuses", %{
287           "status" =>
288             "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
289         })
290
291       real_status = json_response_and_validate_schema(real_conn, 200)
292
293       assert real_status
294       assert Object.get_by_ap_id(real_status["uri"])
295
296       real_status =
297         real_status
298         |> Map.put("id", nil)
299         |> Map.put("url", nil)
300         |> Map.put("uri", nil)
301         |> Map.put("created_at", nil)
302         |> Kernel.put_in(["pleroma", "context"], nil)
303         |> Kernel.put_in(["pleroma", "conversation_id"], nil)
304
305       fake_conn =
306         conn
307         |> assign(:user, refresh_record(conn.assigns.user))
308         |> put_req_header("content-type", "application/json")
309         |> post("/api/v1/statuses", %{
310           "status" =>
311             "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
312           "preview" => true
313         })
314
315       fake_status = json_response_and_validate_schema(fake_conn, 200)
316
317       assert fake_status
318       refute Object.get_by_ap_id(fake_status["uri"])
319
320       fake_status =
321         fake_status
322         |> Map.put("id", nil)
323         |> Map.put("url", nil)
324         |> Map.put("uri", nil)
325         |> Map.put("created_at", nil)
326         |> Kernel.put_in(["pleroma", "context"], nil)
327         |> Kernel.put_in(["pleroma", "conversation_id"], nil)
328
329       assert real_status == fake_status
330     end
331
332     test "posting a direct status", %{conn: conn} do
333       user2 = insert(:user)
334       content = "direct cofe @#{user2.nickname}"
335
336       conn =
337         conn
338         |> put_req_header("content-type", "application/json")
339         |> post("/api/v1/statuses", %{"status" => content, "visibility" => "direct"})
340
341       assert %{"id" => id} = response = json_response_and_validate_schema(conn, 200)
342       assert response["visibility"] == "direct"
343       assert response["pleroma"]["direct_conversation_id"]
344       assert activity = Activity.get_by_id(id)
345       assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
346       assert activity.data["to"] == [user2.ap_id]
347       assert activity.data["cc"] == []
348     end
349
350     test "discloses application metadata when enabled" do
351       user = insert(:user, disclose_client: true)
352       %{user: _user, token: token, conn: conn} = oauth_access(["write:statuses"], user: user)
353
354       %Pleroma.Web.OAuth.Token{
355         app: %Pleroma.Web.OAuth.App{
356           client_name: app_name,
357           website: app_website
358         }
359       } = token
360
361       result =
362         conn
363         |> put_req_header("content-type", "application/json")
364         |> post("/api/v1/statuses", %{
365           "status" => "cofe is my copilot"
366         })
367
368       assert %{
369                "content" => "cofe is my copilot"
370              } = json_response_and_validate_schema(result, 200)
371
372       activity = result.assigns.activity.id
373
374       result =
375         conn
376         |> get("/api/v1/statuses/#{activity}")
377
378       assert %{
379                "content" => "cofe is my copilot",
380                "application" => %{
381                  "name" => ^app_name,
382                  "website" => ^app_website
383                }
384              } = json_response_and_validate_schema(result, 200)
385     end
386
387     test "hides application metadata when disabled" do
388       user = insert(:user, disclose_client: false)
389       %{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
390
391       result =
392         conn
393         |> put_req_header("content-type", "application/json")
394         |> post("/api/v1/statuses", %{
395           "status" => "club mate is my wingman"
396         })
397
398       assert %{"content" => "club mate is my wingman"} =
399                json_response_and_validate_schema(result, 200)
400
401       activity = result.assigns.activity.id
402
403       result =
404         conn
405         |> get("/api/v1/statuses/#{activity}")
406
407       assert %{
408                "content" => "club mate is my wingman",
409                "application" => nil
410              } = json_response_and_validate_schema(result, 200)
411     end
412   end
413
414   describe "posting scheduled statuses" do
415     setup do: oauth_access(["write:statuses"])
416
417     test "creates a scheduled activity", %{conn: conn} do
418       scheduled_at =
419         NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
420         |> NaiveDateTime.to_iso8601()
421         |> Kernel.<>("Z")
422
423       conn =
424         conn
425         |> put_req_header("content-type", "application/json")
426         |> post("/api/v1/statuses", %{
427           "status" => "scheduled",
428           "scheduled_at" => scheduled_at
429         })
430
431       assert %{"scheduled_at" => expected_scheduled_at} =
432                json_response_and_validate_schema(conn, 200)
433
434       assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
435       assert [] == Repo.all(Activity)
436     end
437
438     test "with expiration" do
439       %{conn: conn} = oauth_access(["write:statuses", "read:statuses"])
440
441       scheduled_at =
442         NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
443         |> NaiveDateTime.to_iso8601()
444         |> Kernel.<>("Z")
445
446       assert %{"id" => status_id, "params" => %{"expires_in" => 300}} =
447                conn
448                |> put_req_header("content-type", "application/json")
449                |> post("/api/v1/statuses", %{
450                  "status" => "scheduled",
451                  "scheduled_at" => scheduled_at,
452                  "expires_in" => 300
453                })
454                |> json_response_and_validate_schema(200)
455
456       assert %{"id" => ^status_id, "params" => %{"expires_in" => 300}} =
457                conn
458                |> put_req_header("content-type", "application/json")
459                |> get("/api/v1/scheduled_statuses/#{status_id}")
460                |> json_response_and_validate_schema(200)
461     end
462
463     test "ignores nil values", %{conn: conn} do
464       conn =
465         conn
466         |> put_req_header("content-type", "application/json")
467         |> post("/api/v1/statuses", %{
468           "status" => "not scheduled",
469           "scheduled_at" => nil
470         })
471
472       assert result = json_response_and_validate_schema(conn, 200)
473       assert Activity.get_by_id(result["id"])
474     end
475
476     test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
477       scheduled_at =
478         NaiveDateTime.utc_now()
479         |> NaiveDateTime.add(:timer.minutes(120), :millisecond)
480         |> NaiveDateTime.to_iso8601()
481         |> Kernel.<>("Z")
482
483       file = %Plug.Upload{
484         content_type: "image/jpeg",
485         path: Path.absname("test/fixtures/image.jpg"),
486         filename: "an_image.jpg"
487       }
488
489       {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
490
491       conn =
492         conn
493         |> put_req_header("content-type", "application/json")
494         |> post("/api/v1/statuses", %{
495           "media_ids" => [to_string(upload.id)],
496           "status" => "scheduled",
497           "scheduled_at" => scheduled_at
498         })
499
500       assert %{"media_attachments" => [media_attachment]} =
501                json_response_and_validate_schema(conn, 200)
502
503       assert %{"type" => "image"} = media_attachment
504     end
505
506     test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
507          %{conn: conn} do
508       scheduled_at =
509         NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
510         |> NaiveDateTime.to_iso8601()
511         |> Kernel.<>("Z")
512
513       conn =
514         conn
515         |> put_req_header("content-type", "application/json")
516         |> post("/api/v1/statuses", %{
517           "status" => "not scheduled",
518           "scheduled_at" => scheduled_at
519         })
520
521       assert %{"content" => "not scheduled"} = json_response_and_validate_schema(conn, 200)
522       assert [] == Repo.all(ScheduledActivity)
523     end
524
525     test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do
526       today =
527         NaiveDateTime.utc_now()
528         |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
529         |> NaiveDateTime.to_iso8601()
530         # TODO
531         |> Kernel.<>("Z")
532
533       attrs = %{params: %{}, scheduled_at: today}
534       {:ok, _} = ScheduledActivity.create(user, attrs)
535       {:ok, _} = ScheduledActivity.create(user, attrs)
536
537       conn =
538         conn
539         |> put_req_header("content-type", "application/json")
540         |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
541
542       assert %{"error" => "daily limit exceeded"} == json_response_and_validate_schema(conn, 422)
543     end
544
545     test "returns error when total user limit is exceeded", %{user: user, conn: conn} do
546       today =
547         NaiveDateTime.utc_now()
548         |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
549         |> NaiveDateTime.to_iso8601()
550         |> Kernel.<>("Z")
551
552       tomorrow =
553         NaiveDateTime.utc_now()
554         |> NaiveDateTime.add(:timer.hours(36), :millisecond)
555         |> NaiveDateTime.to_iso8601()
556         |> Kernel.<>("Z")
557
558       attrs = %{params: %{}, scheduled_at: today}
559       {:ok, _} = ScheduledActivity.create(user, attrs)
560       {:ok, _} = ScheduledActivity.create(user, attrs)
561       {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
562
563       conn =
564         conn
565         |> put_req_header("content-type", "application/json")
566         |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
567
568       assert %{"error" => "total limit exceeded"} == json_response_and_validate_schema(conn, 422)
569     end
570   end
571
572   describe "posting polls" do
573     setup do: oauth_access(["write:statuses"])
574
575     test "posting a poll", %{conn: conn} do
576       time = NaiveDateTime.utc_now()
577
578       conn =
579         conn
580         |> put_req_header("content-type", "application/json")
581         |> post("/api/v1/statuses", %{
582           "status" => "Who is the #bestgrill?",
583           "poll" => %{
584             "options" => ["Rei", "Asuka", "Misato"],
585             "expires_in" => 420
586           }
587         })
588
589       response = json_response_and_validate_schema(conn, 200)
590
591       assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
592                title in ["Rei", "Asuka", "Misato"]
593              end)
594
595       assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
596       assert response["poll"]["expired"] == false
597
598       question = Object.get_by_id(response["poll"]["id"])
599
600       # closed contains utc timezone
601       assert question.data["closed"] =~ "Z"
602     end
603
604     test "option limit is enforced", %{conn: conn} do
605       limit = Config.get([:instance, :poll_limits, :max_options])
606
607       conn =
608         conn
609         |> put_req_header("content-type", "application/json")
610         |> post("/api/v1/statuses", %{
611           "status" => "desu~",
612           "poll" => %{
613             "options" => Enum.map(0..limit, fn num -> "desu #{num}" end),
614             "expires_in" => 1
615           }
616         })
617
618       %{"error" => error} = json_response_and_validate_schema(conn, 422)
619       assert error == "Poll can't contain more than #{limit} options"
620     end
621
622     test "option character limit is enforced", %{conn: conn} do
623       limit = Config.get([:instance, :poll_limits, :max_option_chars])
624
625       conn =
626         conn
627         |> put_req_header("content-type", "application/json")
628         |> post("/api/v1/statuses", %{
629           "status" => "...",
630           "poll" => %{
631             "options" => [String.duplicate(".", limit + 1), "lol"],
632             "expires_in" => 1
633           }
634         })
635
636       %{"error" => error} = json_response_and_validate_schema(conn, 422)
637       assert error == "Poll options cannot be longer than #{limit} characters each"
638     end
639
640     test "minimal date limit is enforced", %{conn: conn} do
641       limit = Config.get([:instance, :poll_limits, :min_expiration])
642
643       conn =
644         conn
645         |> put_req_header("content-type", "application/json")
646         |> post("/api/v1/statuses", %{
647           "status" => "imagine arbitrary limits",
648           "poll" => %{
649             "options" => ["this post was made by pleroma gang"],
650             "expires_in" => limit - 1
651           }
652         })
653
654       %{"error" => error} = json_response_and_validate_schema(conn, 422)
655       assert error == "Expiration date is too soon"
656     end
657
658     test "maximum date limit is enforced", %{conn: conn} do
659       limit = Config.get([:instance, :poll_limits, :max_expiration])
660
661       conn =
662         conn
663         |> put_req_header("content-type", "application/json")
664         |> post("/api/v1/statuses", %{
665           "status" => "imagine arbitrary limits",
666           "poll" => %{
667             "options" => ["this post was made by pleroma gang"],
668             "expires_in" => limit + 1
669           }
670         })
671
672       %{"error" => error} = json_response_and_validate_schema(conn, 422)
673       assert error == "Expiration date is too far in the future"
674     end
675
676     test "scheduled poll", %{conn: conn} do
677       clear_config([ScheduledActivity, :enabled], true)
678
679       scheduled_at =
680         NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
681         |> NaiveDateTime.to_iso8601()
682         |> Kernel.<>("Z")
683
684       %{"id" => scheduled_id} =
685         conn
686         |> put_req_header("content-type", "application/json")
687         |> post("/api/v1/statuses", %{
688           "status" => "very cool poll",
689           "poll" => %{
690             "options" => ~w(a b c),
691             "expires_in" => 420
692           },
693           "scheduled_at" => scheduled_at
694         })
695         |> json_response_and_validate_schema(200)
696
697       assert {:ok, %{id: activity_id}} =
698                perform_job(ScheduledActivityWorker, %{
699                  activity_id: scheduled_id
700                })
701
702       refute_enqueued(worker: ScheduledActivityWorker)
703
704       object =
705         Activity
706         |> Repo.get(activity_id)
707         |> Object.normalize()
708
709       assert object.data["content"] == "very cool poll"
710       assert object.data["type"] == "Question"
711       assert length(object.data["oneOf"]) == 3
712     end
713
714     test "cannot have only one option", %{conn: conn} do
715       conn =
716         conn
717         |> put_req_header("content-type", "application/json")
718         |> post("/api/v1/statuses", %{
719           "status" => "desu~",
720           "poll" => %{"options" => ["mew"], "expires_in" => 1}
721         })
722
723       %{"error" => error} = json_response_and_validate_schema(conn, 422)
724       assert error == "Poll must contain at least 2 options"
725     end
726
727     test "cannot have only duplicated options", %{conn: conn} do
728       conn =
729         conn
730         |> put_req_header("content-type", "application/json")
731         |> post("/api/v1/statuses", %{
732           "status" => "desu~",
733           "poll" => %{"options" => ["mew", "mew"], "expires_in" => 1}
734         })
735
736       %{"error" => error} = json_response_and_validate_schema(conn, 422)
737       assert error == "Poll must contain at least 2 options"
738     end
739   end
740
741   test "get a status" do
742     %{conn: conn} = oauth_access(["read:statuses"])
743     activity = insert(:note_activity)
744
745     conn = get(conn, "/api/v1/statuses/#{activity.id}")
746
747     assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
748     assert id == to_string(activity.id)
749   end
750
751   defp local_and_remote_activities do
752     local = insert(:note_activity)
753     remote = insert(:note_activity, local: false)
754     {:ok, local: local, remote: remote}
755   end
756
757   defp local_and_remote_context_activities do
758     local_user_1 = insert(:user)
759     local_user_2 = insert(:user)
760     remote_user = insert(:user, local: false)
761
762     {:ok, %{id: id1, data: %{"context" => context}}} =
763       CommonAPI.post(local_user_1, %{status: "post"})
764
765     {:ok, %{id: id2} = post} =
766       CommonAPI.post(local_user_2, %{status: "local reply", in_reply_to_status_id: id1})
767
768     params = %{
769       "@context" => "https://www.w3.org/ns/activitystreams",
770       "actor" => remote_user.ap_id,
771       "type" => "Create",
772       "context" => context,
773       "id" => "#{remote_user.ap_id}/activities/1",
774       "inReplyTo" => post.data["id"],
775       "object" => %{
776         "type" => "Note",
777         "content" => "remote reply",
778         "context" => context,
779         "id" => "#{remote_user.ap_id}/objects/1",
780         "attributedTo" => remote_user.ap_id,
781         "to" => [
782           local_user_1.ap_id,
783           local_user_2.ap_id,
784           "https://www.w3.org/ns/activitystreams#Public"
785         ]
786       },
787       "to" => [
788         local_user_1.ap_id,
789         local_user_2.ap_id,
790         "https://www.w3.org/ns/activitystreams#Public"
791       ]
792     }
793
794     {:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params)
795     {:ok, remote_activity} = ObanHelpers.perform(job)
796
797     %{locals: [id1, id2], remote: remote_activity.id, context: context}
798   end
799
800   describe "status with restrict unauthenticated activities for local and remote" do
801     setup do: local_and_remote_activities()
802
803     setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
804
805     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
806
807     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
808       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
809
810       assert json_response_and_validate_schema(res_conn, :not_found) == %{
811                "error" => "Record not found"
812              }
813
814       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
815
816       assert json_response_and_validate_schema(res_conn, :not_found) == %{
817                "error" => "Record not found"
818              }
819     end
820
821     test "if user is authenticated", %{local: local, remote: remote} do
822       %{conn: conn} = oauth_access(["read"])
823       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
824       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
825
826       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
827       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
828     end
829   end
830
831   describe "status with restrict unauthenticated activities for local" do
832     setup do: local_and_remote_activities()
833
834     setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
835
836     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
837       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
838
839       assert json_response_and_validate_schema(res_conn, :not_found) == %{
840                "error" => "Record not found"
841              }
842
843       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
844       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
845     end
846
847     test "if user is authenticated", %{local: local, remote: remote} do
848       %{conn: conn} = oauth_access(["read"])
849       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
850       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
851
852       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
853       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
854     end
855   end
856
857   describe "status with restrict unauthenticated activities for remote" do
858     setup do: local_and_remote_activities()
859
860     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
861
862     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
863       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
864       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
865
866       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
867
868       assert json_response_and_validate_schema(res_conn, :not_found) == %{
869                "error" => "Record not found"
870              }
871     end
872
873     test "if user is authenticated", %{local: local, remote: remote} do
874       %{conn: conn} = oauth_access(["read"])
875       res_conn = get(conn, "/api/v1/statuses/#{local.id}")
876       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
877
878       res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
879       assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
880     end
881   end
882
883   test "getting a status that doesn't exist returns 404" do
884     %{conn: conn} = oauth_access(["read:statuses"])
885     activity = insert(:note_activity)
886
887     conn = get(conn, "/api/v1/statuses/#{String.downcase(activity.id)}")
888
889     assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
890   end
891
892   test "get a direct status" do
893     %{user: user, conn: conn} = oauth_access(["read:statuses"])
894     other_user = insert(:user)
895
896     {:ok, activity} =
897       CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "direct"})
898
899     conn =
900       conn
901       |> assign(:user, user)
902       |> get("/api/v1/statuses/#{activity.id}")
903
904     [participation] = Participation.for_user(user)
905
906     res = json_response_and_validate_schema(conn, 200)
907     assert res["pleroma"]["direct_conversation_id"] == participation.id
908   end
909
910   test "get statuses by IDs" do
911     %{conn: conn} = oauth_access(["read:statuses"])
912     %{id: id1} = insert(:note_activity)
913     %{id: id2} = insert(:note_activity)
914
915     query_string = "ids[]=#{id1}&ids[]=#{id2}"
916     conn = get(conn, "/api/v1/statuses/?#{query_string}")
917
918     assert [%{"id" => ^id1}, %{"id" => ^id2}] =
919              Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"])
920   end
921
922   describe "getting statuses by ids with restricted unauthenticated for local and remote" do
923     setup do: local_and_remote_activities()
924
925     setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
926
927     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
928
929     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
930       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
931
932       assert json_response_and_validate_schema(res_conn, 200) == []
933     end
934
935     test "if user is authenticated", %{local: local, remote: remote} do
936       %{conn: conn} = oauth_access(["read"])
937
938       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
939
940       assert length(json_response_and_validate_schema(res_conn, 200)) == 2
941     end
942   end
943
944   describe "getting statuses by ids with restricted unauthenticated for local" do
945     setup do: local_and_remote_activities()
946
947     setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
948
949     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
950       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
951
952       remote_id = remote.id
953       assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200)
954     end
955
956     test "if user is authenticated", %{local: local, remote: remote} do
957       %{conn: conn} = oauth_access(["read"])
958
959       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
960
961       assert length(json_response_and_validate_schema(res_conn, 200)) == 2
962     end
963   end
964
965   describe "getting statuses by ids with restricted unauthenticated for remote" do
966     setup do: local_and_remote_activities()
967
968     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
969
970     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
971       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
972
973       local_id = local.id
974       assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200)
975     end
976
977     test "if user is authenticated", %{local: local, remote: remote} do
978       %{conn: conn} = oauth_access(["read"])
979
980       res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
981
982       assert length(json_response_and_validate_schema(res_conn, 200)) == 2
983     end
984   end
985
986   describe "getting status contexts restricted unauthenticated for local and remote" do
987     setup do: local_and_remote_context_activities()
988
989     setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
990
991     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
992
993     test "if user is unauthenticated", %{conn: conn, locals: [post_id, _]} do
994       res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
995
996       assert json_response_and_validate_schema(res_conn, 200) == %{
997                "ancestors" => [],
998                "descendants" => []
999              }
1000     end
1001
1002     test "if user is unauthenticated reply", %{conn: conn, locals: [_, reply_id]} do
1003       res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
1004
1005       assert json_response_and_validate_schema(res_conn, 200) == %{
1006                "ancestors" => [],
1007                "descendants" => []
1008              }
1009     end
1010
1011     test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do
1012       %{conn: conn} = oauth_access(["read"])
1013       res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
1014
1015       %{"ancestors" => [], "descendants" => descendants} =
1016         json_response_and_validate_schema(res_conn, 200)
1017
1018       descendant_ids =
1019         descendants
1020         |> Enum.map(& &1["id"])
1021
1022       assert reply_id in descendant_ids
1023       assert remote_reply_id in descendant_ids
1024     end
1025
1026     test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do
1027       %{conn: conn} = oauth_access(["read"])
1028       res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
1029
1030       %{"ancestors" => ancestors, "descendants" => descendants} =
1031         json_response_and_validate_schema(res_conn, 200)
1032
1033       ancestor_ids =
1034         ancestors
1035         |> Enum.map(& &1["id"])
1036
1037       descendant_ids =
1038         descendants
1039         |> Enum.map(& &1["id"])
1040
1041       assert post_id in ancestor_ids
1042       assert remote_reply_id in descendant_ids
1043     end
1044   end
1045
1046   describe "getting status contexts restricted unauthenticated for local" do
1047     setup do: local_and_remote_context_activities()
1048
1049     setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
1050
1051     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], false)
1052
1053     test "if user is unauthenticated", %{
1054       conn: conn,
1055       locals: [post_id, reply_id],
1056       remote: remote_reply_id
1057     } do
1058       res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
1059
1060       %{"ancestors" => [], "descendants" => descendants} =
1061         json_response_and_validate_schema(res_conn, 200)
1062
1063       descendant_ids =
1064         descendants
1065         |> Enum.map(& &1["id"])
1066
1067       assert reply_id not in descendant_ids
1068       assert remote_reply_id in descendant_ids
1069     end
1070
1071     test "if user is unauthenticated reply", %{
1072       conn: conn,
1073       locals: [post_id, reply_id],
1074       remote: remote_reply_id
1075     } do
1076       res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
1077
1078       %{"ancestors" => ancestors, "descendants" => descendants} =
1079         json_response_and_validate_schema(res_conn, 200)
1080
1081       ancestor_ids =
1082         ancestors
1083         |> Enum.map(& &1["id"])
1084
1085       descendant_ids =
1086         descendants
1087         |> Enum.map(& &1["id"])
1088
1089       assert post_id not in ancestor_ids
1090       assert remote_reply_id in descendant_ids
1091     end
1092
1093     test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do
1094       %{conn: conn} = oauth_access(["read"])
1095       res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
1096
1097       %{"ancestors" => [], "descendants" => descendants} =
1098         json_response_and_validate_schema(res_conn, 200)
1099
1100       descendant_ids =
1101         descendants
1102         |> Enum.map(& &1["id"])
1103
1104       assert reply_id in descendant_ids
1105       assert remote_reply_id in descendant_ids
1106     end
1107
1108     test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do
1109       %{conn: conn} = oauth_access(["read"])
1110       res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
1111
1112       %{"ancestors" => ancestors, "descendants" => descendants} =
1113         json_response_and_validate_schema(res_conn, 200)
1114
1115       ancestor_ids =
1116         ancestors
1117         |> Enum.map(& &1["id"])
1118
1119       descendant_ids =
1120         descendants
1121         |> Enum.map(& &1["id"])
1122
1123       assert post_id in ancestor_ids
1124       assert remote_reply_id in descendant_ids
1125     end
1126   end
1127
1128   describe "getting status contexts restricted unauthenticated for remote" do
1129     setup do: local_and_remote_context_activities()
1130
1131     setup do: clear_config([:restrict_unauthenticated, :activities, :local], false)
1132
1133     setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
1134
1135     test "if user is unauthenticated", %{
1136       conn: conn,
1137       locals: [post_id, reply_id],
1138       remote: remote_reply_id
1139     } do
1140       res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
1141
1142       %{"ancestors" => [], "descendants" => descendants} =
1143         json_response_and_validate_schema(res_conn, 200)
1144
1145       descendant_ids =
1146         descendants
1147         |> Enum.map(& &1["id"])
1148
1149       assert reply_id in descendant_ids
1150       assert remote_reply_id not in descendant_ids
1151     end
1152
1153     test "if user is unauthenticated reply", %{
1154       conn: conn,
1155       locals: [post_id, reply_id],
1156       remote: remote_reply_id
1157     } do
1158       res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
1159
1160       %{"ancestors" => ancestors, "descendants" => descendants} =
1161         json_response_and_validate_schema(res_conn, 200)
1162
1163       ancestor_ids =
1164         ancestors
1165         |> Enum.map(& &1["id"])
1166
1167       descendant_ids =
1168         descendants
1169         |> Enum.map(& &1["id"])
1170
1171       assert post_id in ancestor_ids
1172       assert remote_reply_id not in descendant_ids
1173     end
1174
1175     test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do
1176       %{conn: conn} = oauth_access(["read"])
1177       res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
1178
1179       %{"ancestors" => [], "descendants" => descendants} =
1180         json_response_and_validate_schema(res_conn, 200)
1181
1182       reply_ids =
1183         descendants
1184         |> Enum.map(& &1["id"])
1185
1186       assert reply_id in reply_ids
1187       assert remote_reply_id in reply_ids
1188     end
1189
1190     test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do
1191       %{conn: conn} = oauth_access(["read"])
1192       res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
1193
1194       %{"ancestors" => ancestors, "descendants" => descendants} =
1195         json_response_and_validate_schema(res_conn, 200)
1196
1197       ancestor_ids =
1198         ancestors
1199         |> Enum.map(& &1["id"])
1200
1201       descendant_ids =
1202         descendants
1203         |> Enum.map(& &1["id"])
1204
1205       assert post_id in ancestor_ids
1206       assert remote_reply_id in descendant_ids
1207     end
1208   end
1209
1210   describe "deleting a status" do
1211     test "when you created it" do
1212       %{user: author, conn: conn} = oauth_access(["write:statuses"])
1213       activity = insert(:note_activity, user: author)
1214       object = Object.normalize(activity, fetch: false)
1215
1216       content = object.data["content"]
1217       source = object.data["source"]
1218
1219       result =
1220         conn
1221         |> assign(:user, author)
1222         |> delete("/api/v1/statuses/#{activity.id}")
1223         |> json_response_and_validate_schema(200)
1224
1225       assert match?(%{"content" => ^content, "text" => ^source}, result)
1226
1227       refute Activity.get_by_id(activity.id)
1228     end
1229
1230     test "when it doesn't exist" do
1231       %{user: author, conn: conn} = oauth_access(["write:statuses"])
1232       activity = insert(:note_activity, user: author)
1233
1234       conn =
1235         conn
1236         |> assign(:user, author)
1237         |> delete("/api/v1/statuses/#{String.downcase(activity.id)}")
1238
1239       assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
1240     end
1241
1242     test "when you didn't create it" do
1243       %{conn: conn} = oauth_access(["write:statuses"])
1244       activity = insert(:note_activity)
1245
1246       conn = delete(conn, "/api/v1/statuses/#{activity.id}")
1247
1248       assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
1249
1250       assert Activity.get_by_id(activity.id) == activity
1251     end
1252
1253     test "when you're privileged to", %{conn: conn} do
1254       clear_config([:instance, :moderator_privileges], [:messages_delete])
1255       activity = insert(:note_activity)
1256       user = insert(:user, is_moderator: true)
1257
1258       res_conn =
1259         conn
1260         |> assign(:user, user)
1261         |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
1262         |> delete("/api/v1/statuses/#{activity.id}")
1263
1264       assert %{} = json_response_and_validate_schema(res_conn, 200)
1265
1266       assert ModerationLog |> Repo.one() |> ModerationLog.get_log_entry_message() ==
1267                "@#{user.nickname} deleted status ##{activity.id}"
1268
1269       refute Activity.get_by_id(activity.id)
1270     end
1271
1272     test "when you're privileged and the user is banned", %{conn: conn} do
1273       clear_config([:instance, :moderator_privileges], [:messages_delete])
1274       posting_user = insert(:user, is_active: false)
1275       refute posting_user.is_active
1276       activity = insert(:note_activity, user: posting_user)
1277       user = insert(:user, is_moderator: true)
1278
1279       res_conn =
1280         conn
1281         |> assign(:user, user)
1282         |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
1283         |> delete("/api/v1/statuses/#{activity.id}")
1284
1285       assert %{} = json_response_and_validate_schema(res_conn, 200)
1286
1287       assert ModerationLog |> Repo.one() |> ModerationLog.get_log_entry_message() ==
1288                "@#{user.nickname} deleted status ##{activity.id}"
1289
1290       refute Activity.get_by_id(activity.id)
1291     end
1292   end
1293
1294   describe "reblogging" do
1295     setup do: oauth_access(["write:statuses"])
1296
1297     test "reblogs and returns the reblogged status", %{conn: conn} do
1298       activity = insert(:note_activity)
1299
1300       conn =
1301         conn
1302         |> put_req_header("content-type", "application/json")
1303         |> post("/api/v1/statuses/#{activity.id}/reblog")
1304
1305       assert %{
1306                "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1307                "reblogged" => true
1308              } = json_response_and_validate_schema(conn, 200)
1309
1310       assert to_string(activity.id) == id
1311     end
1312
1313     test "returns 404 if the reblogged status doesn't exist", %{conn: conn} do
1314       activity = insert(:note_activity)
1315
1316       conn =
1317         conn
1318         |> put_req_header("content-type", "application/json")
1319         |> post("/api/v1/statuses/#{String.downcase(activity.id)}/reblog")
1320
1321       assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
1322     end
1323
1324     test "reblogs privately and returns the reblogged status", %{conn: conn} do
1325       activity = insert(:note_activity)
1326
1327       conn =
1328         conn
1329         |> put_req_header("content-type", "application/json")
1330         |> post(
1331           "/api/v1/statuses/#{activity.id}/reblog",
1332           %{"visibility" => "private"}
1333         )
1334
1335       assert %{
1336                "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1337                "reblogged" => true,
1338                "visibility" => "private"
1339              } = json_response_and_validate_schema(conn, 200)
1340
1341       assert to_string(activity.id) == id
1342     end
1343
1344     test "reblogged status for another user" do
1345       activity = insert(:note_activity)
1346       user1 = insert(:user)
1347       user2 = insert(:user)
1348       user3 = insert(:user)
1349       {:ok, _} = CommonAPI.favorite(user2, activity.id)
1350       {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1351       {:ok, reblog_activity1} = CommonAPI.repeat(activity.id, user1)
1352       {:ok, _} = CommonAPI.repeat(activity.id, user2)
1353
1354       conn_res =
1355         build_conn()
1356         |> assign(:user, user3)
1357         |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1358         |> get("/api/v1/statuses/#{reblog_activity1.id}")
1359
1360       assert %{
1361                "reblog" => %{"id" => _id, "reblogged" => false, "reblogs_count" => 2},
1362                "reblogged" => false,
1363                "favourited" => false,
1364                "bookmarked" => false
1365              } = json_response_and_validate_schema(conn_res, 200)
1366
1367       conn_res =
1368         build_conn()
1369         |> assign(:user, user2)
1370         |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
1371         |> get("/api/v1/statuses/#{reblog_activity1.id}")
1372
1373       assert %{
1374                "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1375                "reblogged" => true,
1376                "favourited" => true,
1377                "bookmarked" => true
1378              } = json_response_and_validate_schema(conn_res, 200)
1379
1380       assert to_string(activity.id) == id
1381     end
1382
1383     test "author can reblog own private status", %{conn: conn, user: user} do
1384       {:ok, activity} = CommonAPI.post(user, %{status: "cofe", visibility: "private"})
1385
1386       conn =
1387         conn
1388         |> put_req_header("content-type", "application/json")
1389         |> post("/api/v1/statuses/#{activity.id}/reblog")
1390
1391       assert %{
1392                "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1393                "reblogged" => true,
1394                "visibility" => "private"
1395              } = json_response_and_validate_schema(conn, 200)
1396
1397       assert to_string(activity.id) == id
1398     end
1399   end
1400
1401   describe "unreblogging" do
1402     setup do: oauth_access(["write:statuses"])
1403
1404     test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
1405       activity = insert(:note_activity)
1406
1407       {:ok, _} = CommonAPI.repeat(activity.id, user)
1408
1409       conn =
1410         conn
1411         |> put_req_header("content-type", "application/json")
1412         |> post("/api/v1/statuses/#{activity.id}/unreblog")
1413
1414       assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} =
1415                json_response_and_validate_schema(conn, 200)
1416
1417       assert to_string(activity.id) == id
1418     end
1419
1420     test "returns 404 error when activity does not exist", %{conn: conn} do
1421       conn =
1422         conn
1423         |> put_req_header("content-type", "application/json")
1424         |> post("/api/v1/statuses/foo/unreblog")
1425
1426       assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1427     end
1428   end
1429
1430   describe "favoriting" do
1431     setup do: oauth_access(["write:favourites"])
1432
1433     test "favs a status and returns it", %{conn: conn} do
1434       activity = insert(:note_activity)
1435
1436       conn =
1437         conn
1438         |> put_req_header("content-type", "application/json")
1439         |> post("/api/v1/statuses/#{activity.id}/favourite")
1440
1441       assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1442                json_response_and_validate_schema(conn, 200)
1443
1444       assert to_string(activity.id) == id
1445     end
1446
1447     test "favoriting twice will just return 200", %{conn: conn} do
1448       activity = insert(:note_activity)
1449
1450       conn
1451       |> put_req_header("content-type", "application/json")
1452       |> post("/api/v1/statuses/#{activity.id}/favourite")
1453
1454       assert conn
1455              |> put_req_header("content-type", "application/json")
1456              |> post("/api/v1/statuses/#{activity.id}/favourite")
1457              |> json_response_and_validate_schema(200)
1458     end
1459
1460     test "returns 404 error for a wrong id", %{conn: conn} do
1461       conn =
1462         conn
1463         |> put_req_header("content-type", "application/json")
1464         |> post("/api/v1/statuses/1/favourite")
1465
1466       assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1467     end
1468   end
1469
1470   describe "unfavoriting" do
1471     setup do: oauth_access(["write:favourites"])
1472
1473     test "unfavorites a status and returns it", %{user: user, conn: conn} do
1474       activity = insert(:note_activity)
1475
1476       {:ok, _} = CommonAPI.favorite(user, activity.id)
1477
1478       conn =
1479         conn
1480         |> put_req_header("content-type", "application/json")
1481         |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1482
1483       assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1484                json_response_and_validate_schema(conn, 200)
1485
1486       assert to_string(activity.id) == id
1487     end
1488
1489     test "returns 404 error for a wrong id", %{conn: conn} do
1490       conn =
1491         conn
1492         |> put_req_header("content-type", "application/json")
1493         |> post("/api/v1/statuses/1/unfavourite")
1494
1495       assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
1496     end
1497   end
1498
1499   describe "pinned statuses" do
1500     setup do: oauth_access(["write:accounts"])
1501
1502     setup %{user: user} do
1503       {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
1504
1505       %{activity: activity}
1506     end
1507
1508     setup do: clear_config([:instance, :max_pinned_statuses], 1)
1509
1510     test "pin status", %{conn: conn, user: user, activity: activity} do
1511       id = activity.id
1512
1513       assert %{"id" => ^id, "pinned" => true} =
1514                conn
1515                |> put_req_header("content-type", "application/json")
1516                |> post("/api/v1/statuses/#{activity.id}/pin")
1517                |> json_response_and_validate_schema(200)
1518
1519       assert [%{"id" => ^id, "pinned" => true}] =
1520                conn
1521                |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1522                |> json_response_and_validate_schema(200)
1523     end
1524
1525     test "non authenticated user", %{activity: activity} do
1526       assert build_conn()
1527              |> put_req_header("content-type", "application/json")
1528              |> post("/api/v1/statuses/#{activity.id}/pin")
1529              |> json_response(403) == %{"error" => "Invalid credentials."}
1530     end
1531
1532     test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
1533       {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
1534
1535       conn =
1536         conn
1537         |> put_req_header("content-type", "application/json")
1538         |> post("/api/v1/statuses/#{dm.id}/pin")
1539
1540       assert json_response_and_validate_schema(conn, 422) == %{
1541                "error" => "Non-public status cannot be pinned"
1542              }
1543     end
1544
1545     test "pin by another user", %{activity: activity} do
1546       %{conn: conn} = oauth_access(["write:accounts"])
1547
1548       assert conn
1549              |> put_req_header("content-type", "application/json")
1550              |> post("/api/v1/statuses/#{activity.id}/pin")
1551              |> json_response(422) == %{"error" => "Someone else's status cannot be pinned"}
1552     end
1553
1554     test "unpin status", %{conn: conn, user: user, activity: activity} do
1555       {:ok, _} = CommonAPI.pin(activity.id, user)
1556       user = refresh_record(user)
1557
1558       id_str = to_string(activity.id)
1559
1560       assert %{"id" => ^id_str, "pinned" => false} =
1561                conn
1562                |> assign(:user, user)
1563                |> post("/api/v1/statuses/#{activity.id}/unpin")
1564                |> json_response_and_validate_schema(200)
1565
1566       assert [] =
1567                conn
1568                |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1569                |> json_response_and_validate_schema(200)
1570     end
1571
1572     test "/unpin: returns 404 error when activity doesn't exist", %{conn: conn} do
1573       assert conn
1574              |> put_req_header("content-type", "application/json")
1575              |> post("/api/v1/statuses/1/unpin")
1576              |> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
1577     end
1578
1579     test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
1580       {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
1581
1582       id_str_one = to_string(activity_one.id)
1583
1584       assert %{"id" => ^id_str_one, "pinned" => true} =
1585                conn
1586                |> put_req_header("content-type", "application/json")
1587                |> post("/api/v1/statuses/#{id_str_one}/pin")
1588                |> json_response_and_validate_schema(200)
1589
1590       user = refresh_record(user)
1591
1592       assert %{"error" => "You have already pinned the maximum number of statuses"} =
1593                conn
1594                |> assign(:user, user)
1595                |> post("/api/v1/statuses/#{activity_two.id}/pin")
1596                |> json_response_and_validate_schema(400)
1597     end
1598
1599     test "on pin removes deletion job, on unpin reschedule deletion" do
1600       %{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
1601       expires_in = 2 * 60 * 60
1602
1603       expires_at = DateTime.add(DateTime.utc_now(), expires_in)
1604
1605       assert %{"id" => id} =
1606                conn
1607                |> put_req_header("content-type", "application/json")
1608                |> post("/api/v1/statuses", %{
1609                  "status" => "oolong",
1610                  "expires_in" => expires_in
1611                })
1612                |> json_response_and_validate_schema(200)
1613
1614       assert_enqueued(
1615         worker: Pleroma.Workers.PurgeExpiredActivity,
1616         args: %{activity_id: id},
1617         scheduled_at: expires_at
1618       )
1619
1620       assert %{"id" => ^id, "pinned" => true} =
1621                conn
1622                |> put_req_header("content-type", "application/json")
1623                |> post("/api/v1/statuses/#{id}/pin")
1624                |> json_response_and_validate_schema(200)
1625
1626       refute_enqueued(
1627         worker: Pleroma.Workers.PurgeExpiredActivity,
1628         args: %{activity_id: id},
1629         scheduled_at: expires_at
1630       )
1631
1632       assert %{"id" => ^id, "pinned" => false} =
1633                conn
1634                |> put_req_header("content-type", "application/json")
1635                |> post("/api/v1/statuses/#{id}/unpin")
1636                |> json_response_and_validate_schema(200)
1637
1638       assert_enqueued(
1639         worker: Pleroma.Workers.PurgeExpiredActivity,
1640         args: %{activity_id: id},
1641         scheduled_at: expires_at
1642       )
1643     end
1644   end
1645
1646   test "bookmarks" do
1647     bookmarks_uri = "/api/v1/bookmarks"
1648
1649     %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
1650     author = insert(:user)
1651
1652     {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
1653     {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
1654
1655     response1 =
1656       conn
1657       |> put_req_header("content-type", "application/json")
1658       |> post("/api/v1/statuses/#{activity1.id}/bookmark")
1659
1660     assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
1661
1662     response2 =
1663       conn
1664       |> put_req_header("content-type", "application/json")
1665       |> post("/api/v1/statuses/#{activity2.id}/bookmark")
1666
1667     assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
1668
1669     bookmarks = get(conn, bookmarks_uri)
1670
1671     assert [
1672              json_response_and_validate_schema(response2, 200),
1673              json_response_and_validate_schema(response1, 200)
1674            ] ==
1675              json_response_and_validate_schema(bookmarks, 200)
1676
1677     response1 =
1678       conn
1679       |> put_req_header("content-type", "application/json")
1680       |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
1681
1682     assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
1683
1684     bookmarks = get(conn, bookmarks_uri)
1685
1686     assert [json_response_and_validate_schema(response2, 200)] ==
1687              json_response_and_validate_schema(bookmarks, 200)
1688   end
1689
1690   test "bookmark folders" do
1691     %{conn: conn, user: user} = oauth_access(["write:bookmarks", "read:bookmarks"])
1692
1693     {:ok, folder} = Pleroma.BookmarkFolder.create(user.id, "folder")
1694     author = insert(:user)
1695
1696     folder_bookmarks_uri = "/api/v1/bookmarks?folder_id=#{folder.id}"
1697
1698     {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
1699     {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
1700
1701     # Add bookmark with a folder
1702     response =
1703       conn
1704       |> put_req_header("content-type", "application/json")
1705       |> post("/api/v1/statuses/#{activity1.id}/bookmark", %{folder_id: folder.id})
1706
1707     assert json_response_and_validate_schema(response, 200)["bookmarked"] == true
1708
1709     assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] ==
1710              folder.id
1711
1712     response =
1713       conn
1714       |> put_req_header("content-type", "application/json")
1715       |> post("/api/v1/statuses/#{activity2.id}/bookmark")
1716
1717     assert json_response_and_validate_schema(response, 200)["bookmarked"] == true
1718     assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] == nil
1719
1720     bookmarks =
1721       get(conn, folder_bookmarks_uri)
1722       |> json_response_and_validate_schema(200)
1723
1724     assert length(bookmarks) == 1
1725
1726     # Update folder for existing bookmark
1727     response =
1728       conn
1729       |> put_req_header("content-type", "application/json")
1730       |> post("/api/v1/statuses/#{activity2.id}/bookmark", %{folder_id: folder.id})
1731
1732     assert json_response_and_validate_schema(response, 200)["bookmarked"] == true
1733
1734     assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] ==
1735              folder.id
1736
1737     bookmarks =
1738       get(conn, folder_bookmarks_uri)
1739       |> json_response_and_validate_schema(200)
1740
1741     assert length(bookmarks) == 2
1742   end
1743
1744   describe "conversation muting" do
1745     setup do: oauth_access(["write:mutes"])
1746
1747     setup do
1748       post_user = insert(:user)
1749       {:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
1750       %{activity: activity}
1751     end
1752
1753     test "mute conversation", %{conn: conn, activity: activity} do
1754       id_str = to_string(activity.id)
1755
1756       assert %{"id" => ^id_str, "muted" => true} =
1757                conn
1758                |> put_req_header("content-type", "application/json")
1759                |> post("/api/v1/statuses/#{activity.id}/mute")
1760                |> json_response_and_validate_schema(200)
1761     end
1762
1763     test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
1764       {:ok, _} = CommonAPI.add_mute(user, activity)
1765
1766       conn =
1767         conn
1768         |> put_req_header("content-type", "application/json")
1769         |> post("/api/v1/statuses/#{activity.id}/mute")
1770
1771       assert json_response_and_validate_schema(conn, 400) == %{
1772                "error" => "conversation is already muted"
1773              }
1774     end
1775
1776     test "unmute conversation", %{conn: conn, user: user, activity: activity} do
1777       {:ok, _} = CommonAPI.add_mute(user, activity)
1778
1779       id_str = to_string(activity.id)
1780
1781       assert %{"id" => ^id_str, "muted" => false} =
1782                conn
1783                # |> assign(:user, user)
1784                |> post("/api/v1/statuses/#{activity.id}/unmute")
1785                |> json_response_and_validate_schema(200)
1786     end
1787   end
1788
1789   test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
1790     user1 = insert(:user)
1791     user2 = insert(:user)
1792     user3 = insert(:user)
1793
1794     {:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
1795
1796     # Reply to status from another user
1797     conn1 =
1798       conn
1799       |> assign(:user, user2)
1800       |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
1801       |> put_req_header("content-type", "application/json")
1802       |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1803
1804     assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
1805
1806     activity = Activity.get_by_id_with_object(id)
1807
1808     assert Object.normalize(activity, fetch: false).data["inReplyTo"] ==
1809              Object.normalize(replied_to, fetch: false).data["id"]
1810
1811     assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1812
1813     # Reblog from the third user
1814     conn2 =
1815       conn
1816       |> assign(:user, user3)
1817       |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
1818       |> put_req_header("content-type", "application/json")
1819       |> post("/api/v1/statuses/#{activity.id}/reblog")
1820
1821     assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1822              json_response_and_validate_schema(conn2, 200)
1823
1824     assert to_string(activity.id) == id
1825
1826     # Getting third user status
1827     conn3 =
1828       conn
1829       |> assign(:user, user3)
1830       |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
1831       |> get("/api/v1/timelines/home")
1832
1833     [reblogged_activity] = json_response_and_validate_schema(conn3, 200)
1834
1835     assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1836
1837     replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1838     assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1839   end
1840
1841   describe "GET /api/v1/statuses/:id/favourited_by" do
1842     setup do: oauth_access(["read:accounts"])
1843
1844     setup %{user: user} do
1845       {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1846
1847       %{activity: activity}
1848     end
1849
1850     test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1851       other_user = insert(:user)
1852       {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1853
1854       response =
1855         conn
1856         |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1857         |> json_response_and_validate_schema(:ok)
1858
1859       [%{"id" => id}] = response
1860
1861       assert id == other_user.id
1862     end
1863
1864     test "returns empty array when status has not been favorited yet", %{
1865       conn: conn,
1866       activity: activity
1867     } do
1868       response =
1869         conn
1870         |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1871         |> json_response_and_validate_schema(:ok)
1872
1873       assert Enum.empty?(response)
1874     end
1875
1876     test "does not return users who have favorited the status but are blocked", %{
1877       conn: %{assigns: %{user: user}} = conn,
1878       activity: activity
1879     } do
1880       other_user = insert(:user)
1881       {:ok, _user_relationship} = User.block(user, other_user)
1882
1883       {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1884
1885       response =
1886         conn
1887         |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1888         |> json_response_and_validate_schema(:ok)
1889
1890       assert Enum.empty?(response)
1891     end
1892
1893     test "does not fail on an unauthenticated request", %{activity: activity} do
1894       other_user = insert(:user)
1895       {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1896
1897       response =
1898         build_conn()
1899         |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1900         |> json_response_and_validate_schema(:ok)
1901
1902       [%{"id" => id}] = response
1903       assert id == other_user.id
1904     end
1905
1906     test "requires authentication for private posts", %{user: user} do
1907       other_user = insert(:user)
1908
1909       {:ok, activity} =
1910         CommonAPI.post(user, %{
1911           status: "@#{other_user.nickname} wanna get some #cofe together?",
1912           visibility: "direct"
1913         })
1914
1915       {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1916
1917       favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
1918
1919       build_conn()
1920       |> get(favourited_by_url)
1921       |> json_response_and_validate_schema(404)
1922
1923       conn =
1924         build_conn()
1925         |> assign(:user, other_user)
1926         |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
1927
1928       conn
1929       |> assign(:token, nil)
1930       |> get(favourited_by_url)
1931       |> json_response_and_validate_schema(404)
1932
1933       response =
1934         conn
1935         |> get(favourited_by_url)
1936         |> json_response_and_validate_schema(200)
1937
1938       [%{"id" => id}] = response
1939       assert id == other_user.id
1940     end
1941
1942     test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
1943       clear_config([:instance, :show_reactions], false)
1944
1945       other_user = insert(:user)
1946       {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1947
1948       response =
1949         conn
1950         |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1951         |> json_response_and_validate_schema(:ok)
1952
1953       assert Enum.empty?(response)
1954     end
1955   end
1956
1957   describe "GET /api/v1/statuses/:id/reblogged_by" do
1958     setup do: oauth_access(["read:accounts"])
1959
1960     setup %{user: user} do
1961       {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1962
1963       %{activity: activity}
1964     end
1965
1966     test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1967       other_user = insert(:user)
1968       {:ok, _} = CommonAPI.repeat(activity.id, other_user)
1969
1970       response =
1971         conn
1972         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1973         |> json_response_and_validate_schema(:ok)
1974
1975       [%{"id" => id}] = response
1976
1977       assert id == other_user.id
1978     end
1979
1980     test "returns empty array when status has not been reblogged yet", %{
1981       conn: conn,
1982       activity: activity
1983     } do
1984       response =
1985         conn
1986         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1987         |> json_response_and_validate_schema(:ok)
1988
1989       assert Enum.empty?(response)
1990     end
1991
1992     test "does not return users who have reblogged the status but are blocked", %{
1993       conn: %{assigns: %{user: user}} = conn,
1994       activity: activity
1995     } do
1996       other_user = insert(:user)
1997       {:ok, _user_relationship} = User.block(user, other_user)
1998
1999       {:ok, _} = CommonAPI.repeat(activity.id, other_user)
2000
2001       response =
2002         conn
2003         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
2004         |> json_response_and_validate_schema(:ok)
2005
2006       assert Enum.empty?(response)
2007     end
2008
2009     test "does not return users who have reblogged the status privately", %{
2010       conn: conn
2011     } do
2012       other_user = insert(:user)
2013       {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
2014
2015       {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
2016
2017       response =
2018         conn
2019         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
2020         |> json_response_and_validate_schema(:ok)
2021
2022       assert Enum.empty?(response)
2023     end
2024
2025     test "does not fail on an unauthenticated request", %{activity: activity} do
2026       other_user = insert(:user)
2027       {:ok, _} = CommonAPI.repeat(activity.id, other_user)
2028
2029       response =
2030         build_conn()
2031         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
2032         |> json_response_and_validate_schema(:ok)
2033
2034       [%{"id" => id}] = response
2035       assert id == other_user.id
2036     end
2037
2038     test "requires authentication for private posts", %{user: user} do
2039       other_user = insert(:user)
2040
2041       {:ok, activity} =
2042         CommonAPI.post(user, %{
2043           status: "@#{other_user.nickname} wanna get some #cofe together?",
2044           visibility: "direct"
2045         })
2046
2047       build_conn()
2048       |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
2049       |> json_response_and_validate_schema(404)
2050
2051       response =
2052         build_conn()
2053         |> assign(:user, other_user)
2054         |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
2055         |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
2056         |> json_response_and_validate_schema(200)
2057
2058       assert [] == response
2059     end
2060   end
2061
2062   test "context" do
2063     user = insert(:user)
2064
2065     {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
2066     {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
2067     {:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
2068     {:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
2069     {:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
2070
2071     response =
2072       build_conn()
2073       |> get("/api/v1/statuses/#{id3}/context")
2074       |> json_response_and_validate_schema(:ok)
2075
2076     assert %{
2077              "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
2078              "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
2079            } = response
2080   end
2081
2082   test "favorites paginate correctly" do
2083     %{user: user, conn: conn} = oauth_access(["read:favourites"])
2084     other_user = insert(:user)
2085     {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
2086     {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
2087     {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
2088
2089     {:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
2090     {:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
2091     {:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
2092
2093     result =
2094       conn
2095       |> get("/api/v1/favourites?limit=1")
2096
2097     assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
2098     assert post_id == second_post.id
2099
2100     # Using the header for pagination works correctly
2101     [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
2102     [next_url, _next_rel] = String.split(next, ";")
2103     next_url = String.trim_trailing(next_url, ">") |> String.trim_leading("<")
2104
2105     max_id = Helpers.get_query_parameter(next_url, "max_id")
2106
2107     assert max_id == third_favorite.id
2108
2109     result =
2110       conn
2111       |> get("/api/v1/favourites?max_id=#{max_id}")
2112
2113     assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
2114              json_response_and_validate_schema(result, 200)
2115
2116     assert first_post_id == first_post.id
2117     assert third_post_id == third_post.id
2118   end
2119
2120   test "returns the favorites of a user" do
2121     %{user: user, conn: conn} = oauth_access(["read:favourites"])
2122     other_user = insert(:user)
2123
2124     {:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
2125     {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
2126
2127     {:ok, last_like} = CommonAPI.favorite(user, activity.id)
2128
2129     first_conn = get(conn, "/api/v1/favourites")
2130
2131     assert [status] = json_response_and_validate_schema(first_conn, 200)
2132     assert status["id"] == to_string(activity.id)
2133
2134     assert [{"link", _link_header}] =
2135              Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
2136
2137     # Honours query params
2138     {:ok, second_activity} =
2139       CommonAPI.post(other_user, %{
2140         status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
2141       })
2142
2143     {:ok, _} = CommonAPI.favorite(user, second_activity.id)
2144
2145     second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
2146
2147     assert [second_status] = json_response_and_validate_schema(second_conn, 200)
2148     assert second_status["id"] == to_string(second_activity.id)
2149
2150     third_conn = get(conn, "/api/v1/favourites?limit=0")
2151
2152     assert [] = json_response_and_validate_schema(third_conn, 200)
2153   end
2154
2155   test "expires_at is nil for another user" do
2156     %{conn: conn, user: user} = oauth_access(["read:statuses"])
2157     expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
2158     {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
2159
2160     assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
2161              conn
2162              |> get("/api/v1/statuses/#{activity.id}")
2163              |> json_response_and_validate_schema(:ok)
2164
2165     {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
2166     assert DateTime.diff(expires_at, a_expires_at) == 0
2167
2168     %{conn: conn} = oauth_access(["read:statuses"])
2169
2170     assert %{"pleroma" => %{"expires_at" => nil}} =
2171              conn
2172              |> get("/api/v1/statuses/#{activity.id}")
2173              |> json_response_and_validate_schema(:ok)
2174   end
2175
2176   describe "local-only statuses" do
2177     test "posting a local only status" do
2178       %{user: _user, conn: conn} = oauth_access(["write:statuses"])
2179
2180       conn_one =
2181         conn
2182         |> put_req_header("content-type", "application/json")
2183         |> post("/api/v1/statuses", %{
2184           "status" => "cofe",
2185           "visibility" => "local"
2186         })
2187
2188       local = Utils.as_local_public()
2189
2190       assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
2191                json_response_and_validate_schema(conn_one, 200)
2192
2193       assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
2194     end
2195
2196     test "other users can read local-only posts" do
2197       user = insert(:user)
2198       %{user: _reader, conn: conn} = oauth_access(["read:statuses"])
2199
2200       {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
2201
2202       received =
2203         conn
2204         |> get("/api/v1/statuses/#{activity.id}")
2205         |> json_response_and_validate_schema(:ok)
2206
2207       assert received["id"] == activity.id
2208     end
2209
2210     test "anonymous users cannot see local-only posts" do
2211       user = insert(:user)
2212
2213       {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
2214
2215       _received =
2216         build_conn()
2217         |> get("/api/v1/statuses/#{activity.id}")
2218         |> json_response_and_validate_schema(:not_found)
2219     end
2220   end
2221
2222   describe "muted reactions" do
2223     test "index" do
2224       %{conn: conn, user: user} = oauth_access(["read:statuses"])
2225
2226       other_user = insert(:user)
2227       {:ok, activity} = CommonAPI.post(user, %{status: "test"})
2228
2229       {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
2230       User.mute(user, other_user)
2231
2232       result =
2233         conn
2234         |> get("/api/v1/statuses/?ids[]=#{activity.id}")
2235         |> json_response_and_validate_schema(200)
2236
2237       assert [
2238                %{
2239                  "pleroma" => %{
2240                    "emoji_reactions" => []
2241                  }
2242                }
2243              ] = result
2244
2245       result =
2246         conn
2247         |> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true")
2248         |> json_response_and_validate_schema(200)
2249
2250       assert [
2251                %{
2252                  "pleroma" => %{
2253                    "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
2254                  }
2255                }
2256              ] = result
2257     end
2258
2259     test "show" do
2260       # %{conn: conn, user: user, token: token} = oauth_access(["read:statuses"])
2261       %{conn: conn, user: user, token: _token} = oauth_access(["read:statuses"])
2262
2263       other_user = insert(:user)
2264       {:ok, activity} = CommonAPI.post(user, %{status: "test"})
2265
2266       {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
2267       User.mute(user, other_user)
2268
2269       result =
2270         conn
2271         |> get("/api/v1/statuses/#{activity.id}")
2272         |> json_response_and_validate_schema(200)
2273
2274       assert %{
2275                "pleroma" => %{
2276                  "emoji_reactions" => []
2277                }
2278              } = result
2279
2280       result =
2281         conn
2282         |> get("/api/v1/statuses/#{activity.id}?with_muted=true")
2283         |> json_response_and_validate_schema(200)
2284
2285       assert %{
2286                "pleroma" => %{
2287                  "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
2288                }
2289              } = result
2290     end
2291   end
2292
2293   describe "get status history" do
2294     setup do
2295       %{conn: build_conn()}
2296     end
2297
2298     test "unedited post", %{conn: conn} do
2299       activity = insert(:note_activity)
2300
2301       conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
2302
2303       assert [_] = json_response_and_validate_schema(conn, 200)
2304     end
2305
2306     test "edited post", %{conn: conn} do
2307       note =
2308         insert(
2309           :note,
2310           data: %{
2311             "formerRepresentations" => %{
2312               "type" => "OrderedCollection",
2313               "orderedItems" => [
2314                 %{
2315                   "type" => "Note",
2316                   "content" => "mew mew 2",
2317                   "summary" => "title 2"
2318                 },
2319                 %{
2320                   "type" => "Note",
2321                   "content" => "mew mew 1",
2322                   "summary" => "title 1"
2323                 }
2324               ],
2325               "totalItems" => 2
2326             }
2327           }
2328         )
2329
2330       activity = insert(:note_activity, note: note)
2331
2332       conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
2333
2334       assert [%{"spoiler_text" => "title 1"}, %{"spoiler_text" => "title 2"}, _] =
2335                json_response_and_validate_schema(conn, 200)
2336     end
2337   end
2338
2339   describe "get status source" do
2340     setup do
2341       %{conn: build_conn()}
2342     end
2343
2344     test "it returns the source", %{conn: conn} do
2345       user = insert(:user)
2346
2347       {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
2348
2349       conn = get(conn, "/api/v1/statuses/#{activity.id}/source")
2350
2351       id = activity.id
2352
2353       assert %{"id" => ^id, "text" => "mew mew #abc", "spoiler_text" => "#def"} =
2354                json_response_and_validate_schema(conn, 200)
2355     end
2356   end
2357
2358   describe "update status" do
2359     setup do
2360       oauth_access(["write:statuses"])
2361     end
2362
2363     test "it updates the status" do
2364       %{conn: conn, user: user} = oauth_access(["write:statuses", "read:statuses"])
2365
2366       {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
2367
2368       conn
2369       |> get("/api/v1/statuses/#{activity.id}")
2370       |> json_response_and_validate_schema(200)
2371
2372       response =
2373         conn
2374         |> put_req_header("content-type", "application/json")
2375         |> put("/api/v1/statuses/#{activity.id}", %{
2376           "status" => "edited",
2377           "spoiler_text" => "lol"
2378         })
2379         |> json_response_and_validate_schema(200)
2380
2381       assert response["content"] == "edited"
2382       assert response["spoiler_text"] == "lol"
2383
2384       response =
2385         conn
2386         |> get("/api/v1/statuses/#{activity.id}")
2387         |> json_response_and_validate_schema(200)
2388
2389       assert response["content"] == "edited"
2390       assert response["spoiler_text"] == "lol"
2391     end
2392
2393     test "it updates the attachments", %{conn: conn, user: user} do
2394       attachment = insert(:attachment, user: user)
2395       attachment_id = to_string(attachment.id)
2396
2397       {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
2398
2399       response =
2400         conn
2401         |> put_req_header("content-type", "application/json")
2402         |> put("/api/v1/statuses/#{activity.id}", %{
2403           "status" => "mew mew #abc",
2404           "spoiler_text" => "#def",
2405           "media_ids" => [attachment_id]
2406         })
2407         |> json_response_and_validate_schema(200)
2408
2409       assert [%{"id" => ^attachment_id}] = response["media_attachments"]
2410     end
2411
2412     test "it does not update visibility", %{conn: conn, user: user} do
2413       {:ok, activity} =
2414         CommonAPI.post(user, %{
2415           status: "mew mew #abc",
2416           spoiler_text: "#def",
2417           visibility: "private"
2418         })
2419
2420       response =
2421         conn
2422         |> put_req_header("content-type", "application/json")
2423         |> put("/api/v1/statuses/#{activity.id}", %{
2424           "status" => "edited",
2425           "spoiler_text" => "lol"
2426         })
2427         |> json_response_and_validate_schema(200)
2428
2429       assert response["visibility"] == "private"
2430     end
2431
2432     test "it refuses to update when original post is not by the user", %{conn: conn} do
2433       another_user = insert(:user)
2434
2435       {:ok, activity} =
2436         CommonAPI.post(another_user, %{status: "mew mew #abc", spoiler_text: "#def"})
2437
2438       conn
2439       |> put_req_header("content-type", "application/json")
2440       |> put("/api/v1/statuses/#{activity.id}", %{
2441         "status" => "edited",
2442         "spoiler_text" => "lol"
2443       })
2444       |> json_response_and_validate_schema(:forbidden)
2445     end
2446
2447     test "it returns 404 if the user cannot see the post", %{conn: conn} do
2448       another_user = insert(:user)
2449
2450       {:ok, activity} =
2451         CommonAPI.post(another_user, %{
2452           status: "mew mew #abc",
2453           spoiler_text: "#def",
2454           visibility: "private"
2455         })
2456
2457       conn
2458       |> put_req_header("content-type", "application/json")
2459       |> put("/api/v1/statuses/#{activity.id}", %{
2460         "status" => "edited",
2461         "spoiler_text" => "lol"
2462       })
2463       |> json_response_and_validate_schema(:not_found)
2464     end
2465   end
2466 end